In
If you start with the design in the prior post, you might discover the BigFramework has to call you back. To get called back the client of the BigFramework has to inherit from BigFrameworkClient. If we add that to OurCode, we have the bad dependency back again. We can let the framework call the adapter back, then the adaptor can callback to OurCode. But then there is a circular dependency;
The problem you might run into in this design is that the adaptor can then only be used with a single client class. If we have other classes using the same services that need to be called back, this won’t do. We need to decouple the callback to OurCode with another interface. I call it OurServiceClient.
Notice the direction of all the dependency arrows. OurCode has stayed decoupled from the BigFramework. It can be tested independent of the BigFramework.
OK, but what does the code look like. Really all we want to do is check OurCode to make sure it does its thing right, but the BigFramework is dragging all these dependencies along as you can see from this code fragment.
class BigFrameworkClient; class BigFramework { public: BigFramework(); virtual ~BigFramework(); static BigFramework* registerMe(BigFrameworkClient*); void doSomethingBig(); void doSomethingIdontWantToUse(); void etc(); //plus many other dependencies i don't want }; |
To break the dependency on the stuff we don’t care about and are keeping us form testing, we define the perfect interface for our purposes (using the Interface Segregation Principle). In C++ a class with all pure virtual methods, like the following code, specifies an interface. This is similar to an interface in Java.
class OurServiceClient; class OurServiceRequests { public: OurServiceRequests(); virtual ~OurServiceRequests(); virtual void doSomething(OurServiceClient*) = 0; }; |
Notice that doSomething() requires a parameter so we can be called back when the BigFramework finishes it job asynchronously. It’s looks like this:
class OurServiceClient { public: OurServiceClient(); virtual ~OurServiceClient(); virtual void doSomethingIsDone() = 0; }; |
The adapter translates OurCode calls into calls on the BigFramework. Later the adapter accepts the BigFramework callbacks, finally notifying OurCode via doSomethingIsDone(). The adaptor header file has some nasty dependencies on the BigFramework, but being a big bad dependency holder is its job. It inherits from the BigFrameworkClient (often an implemented class) and also provides the OurServiceRequests interface. Notice that I defined the BigFrameworkClient as privately inherited. So no outsiders can call the BigFrameworkClient methods on BFwAdaptor. In Java you would probably use and anonymous inner class to implement a BigFrameworkClient callback.
#include "BigFramework.h" #include "BigFrameworkClient.h" #include "OurServiceRequests.h" class OurServiceClient; class BFwAdaptor : public OurServiceRequests , private BigFrameworkClient { public: BFwAdaptor(); virtual ~BFwAdaptor(); virtual void yourRequestIsDone(int result); void doSomething(OurServiceClient*); private: OurServiceClient* client; BigFramework* server; }; |
The adaptor registers with the framework during construction, getting back a BigFramework server to use.
#include "BFwAdaptor.h" #include "BigFramework.h" #include "OurServiceClient.h" BFwAdaptor::BFwAdaptor() { server = BigFramework::registerMe(this); } |
When OurCode wants to doSomething(), it passes in its callback interface. The adaptor remembers its client so it can be called back later when BigFramework is done. The adaptor translates any parameters to BigFramework types as needed. Then asks the framework to doSomethingBig().
void BFwAdaptor::doSomething(OurServiceClient* client) { //a real adaptor might have to do some parameter //translation on the way back too. this->client = client; server->doSomethingBig(); } |
Later, when the BigFramework is done, it calls back on the BigFrameworkClient base class; the adaptor finishes the job and let’s OurCode know what has happened. Again the adaptor may have to translate data types in the process.
void BFwAdaptor::yourRequestIsDone(int result) { //a real adaptor might have to do some parameter //translation to call the BigFramework. client->doSomethingIsDone(); } |
OurCode implements the OurServiceClient interface so we can get called back. (You could use a separate class to implement the interface and collect the callbacks.) Notice doMyThing… We are going to all this trouble to decouple doing my thing from the BigFramework so we can test it (and practice good dependency management). Notice doSomeThingIsDone() is private as is the inheritance from OurServiceClient. It’s no one’s business, so hide it. Again in Java an anonymous inner class would be good way to go.
#include "OurServiceClient.h" class OurServiceRequests; class OurCode : private OurServiceClient { public: OurCode(OurServiceRequests*); virtual ~OurCode(); void doMyThing(); private: OurServiceRequests* server; void doSomethingIsDone(); }; |
The implementation looks like this. I left out the initialization and cleanup code needed in the constructor and destructor. I also left out the business logic that is all about doing our thing. See the call to the adapter through the OurServiceRequests interface. We pass “this” and even though we privately inherited OurServiceClient, “this” fits through that interface fine because as the class itself doMyThing() has access to the private base class.
#include "OurCode.h" #include "OurServiceRequests.h" OurCode::OurCode(OurServiceRequests* server) : server(server) { } OurCode::~OurCode() { } void OurCode::doMyThing() { //do my thing, but get some help from the framework server->doSomething(this); } void OurCode::doSomethingIsDone() { //finish doing my thing now that the result is back; } |
The start up code looks like this:
#include "StartOurCode.h" #include "OurCode.h" #include "BFwAdaptor.h" StartOurCode::StartOurCode() { BFwAdaptor a; OurCode OurCode(&a); OurCode.doMyThing(); //and the system runs } StartOurCode::~StartOurCode() { } |
Here is an alternative design that could be used when OurCode is running in its own thread or process. We could use message passing as a way to break the dependency. When the BigFramework calls back to the adaptor the adapter could send a message to the client’s message queue. The client picks up the message and processes the results.
Again, notice the direction of all the dependency arrows. OurCode has stayed decoupled from the BigFramework.