Let's say my project uses a library, LibFoo
, which provides its functionality through a number of classes, say, FooA
and FooB
. Now, there are a number of similar libraries (e.g., LibBar
which provides BarA
and BarB
) that provide the same functionality as LibFoo
and I want users of my project to be able to chose which library to use, preferably at runtime.
For this to work, I've created a "wrapper layer" that defines the interfaces I expect from the libraries. In my example, this layer contains two interfaces: IfaceA
and IfaceB
. Then, for every library I want to support, I create an "implementation layer" that implements the interfaces using one of the libraries.
My problem now lies in how to implement the implementation layer nicely. To demonstrate my problem, consider we have the following interfaces (shown in C++ but should be applicable to similar languages):
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
class IfaceB
{
...
};
The classes in the implementation layer of LibFoo
will hold objects from the corresponding classes of LibFoo
. The operations in the interfaces should be implemented using these objects. Hence (excuse me for the horrible names):
class ConcreteAForFoo : public IfaceA
{
public:
void doSomethingWith(IfaceB& b) override
{
// This should be implemented using FooA::doSomethingWith(FooB&)
}
private:
FooA a;
};
class ConcreteBForFoo : public IfaceB
{
public:
FooB& getFooB() const
{
return b;
}
private:
FooB b;
};
The problem is in implementing ConcreteAForFoo::doSomethingWith
: its parameter has type IfaceB&
but I need access to the implementation details of ConcreteBForFoo
to be able to implement the method correctly. The only way I've found to do this is to use ugly downcasts all over the place:
void doSomethingWith(IfaceB& b) override
{
assert(dynamic_cast<ConcreteBForFoo*>(&b) != nullptr);
a.doSomethingWith(static_cast<ConcreteBForFoo&>(b).getFooB());
}
Since having to downcast is generally considered to be a code smell, I can't help to think there should be a better way to do this. Or am I designing this wrongly to begin with?
TL;DR
Given a layer of interdependent interfaces (in that methods in one interface receive references to other interfaces). How can the implementations of these interfaces share implementation details without downcasting or exposing those details in the interfaces?
This is not an easy task. The type system off C++ is not quite adequate. Nothing in principle can (statically) prevent your users to instantiate IFaceA
from one library and IFaceB
from the other library, and then mix and match them as they see fit. Your options are:
Don't make the libraries dynamically selectable, i.e. don't make them implement same interfaces. Instead, let them implement instances of a family of interfaces.
template <typename tag>
class IfaceA;
template <typename tag>
class IfaceB;
template <typename tag>
class IfaceA
{
virtual void doSomethingWith(IfaceB<tag>& b) = 0;
};
Each library implements interfaces with a different tag. Tags can be easily selectable by the user at compile time, but not at run time.
Here is a basic example of double dispatch:
//=================
class IFaceBDispatcher;
class IFaceB
{
IFaceBDispatcher* get() = 0;
};
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
// IFaceBDispatcher is incomplete up until this point
//=================================================
// IFaceBDispatcher is defined in these modules
class IFaceBDispatcher
{
virtual void DispatchWithConcreteAForFoo(ConcreteAForFoo*) { throw("unimplemented"); }
virtual void DispatchWithConcreteAForBar(ConcreteAForBar*) { throw("unimplemented"); }
};
class ConcreteAForFoo : public IfaceA
{
virtual void doSomethingWith(IfaceB& b) { b.DispatchWithConcreteAForFoo(this); }
}
class IFaceBDispatcherForFoo : public IFaceBDispatcher
{
ConcreteBForFoo* b;
void DispatchWithConcreteAForFoo(ConcreteAForFoo* a) { a->doSomethingWith(*b); }
};
class IFaceBDispatcherForBar : public IFaceBDispatcher
{
ConcreteBForBar* b;
void DispatchWithConcreteAForBar(ConcreteAForBar* a) { a->doSomethingWith(*b); }
};
class ConcreteBForFoo : public IFaceB
{
IFaceBDispatcher* get() { return new IFaceBDispatcherForFoo{this}; }
};
class ConcreteBForBar : public IFaceB
{
IFaceBDispatcher* get() { return new IFaceBDispatcherForBar{this}; }
};