Stream is the class from which HardwareSerial and SoftwareSerial derive (in the parlance, they inherit from Stream). BTW, Stream inherits from Print.
So, you could modify the library to use the Stream class and then let your application program decided which physical interface (hardware or software) will actually be used.
Here's a couple simple examples using both HardwareSerial and SoftwareSerial. For simplicity, I put all the code in single .ino files. Of course, it could be broken out into separate .h, .cpp, and .ino files:
HardwareSerial:
// ------------- Could be placed in .h file ------------------------
class myClass {
private:
Stream *streamPointer;
public:
void begin(Stream &);
void echo(void);
};
// ------------- Could be placed in .cpp file ------------------------
void myClass::begin(Stream &s) {
streamPointer = &s;
}
void myClass::echo() {
char c;
while (streamPointer->available()) {
c = streamPointer->read();
streamPointer->write(c);
}
}
// ----------------- Main .ino file ------------------------
myClass myObject;
void setup() {
Serial1.begin(115200);
myObject.begin(Serial1);
delay(1000);
}
void loop() {
myObject.echo();
}
SoftwareSerial:
// ------------- Could be placed in .h file ------------------------
class myClass {
private:
Stream *streamPointer;
public:
void begin(Stream &);
void echo(void);
};
// ------------- Could be placed in .cpp file ------------------------
void myClass::begin(Stream &s) {
streamPointer = &s;
}
void myClass::echo() {
char c;
while (streamPointer->available()) {
c = streamPointer->read();
streamPointer->write(c);
}
}
// ----------------- Main .ino file ------------------------
#include <SoftwareSerial.h>
myClass myObject;
SoftwareSerial mySoftwareSerial(5, 7);
void setup() {
mySoftwareSerial.begin(9600);
myObject.begin(mySoftwareSerial);
delay(1000);
}
void loop() {
myObject.echo();
}
Note how the library code is unchanged between these two cases.