I have a legacy project with many classes with high coupling, and I have a necessity to test some of them. So, I decided to introduce dependency injection principle but stumbled upon where to create dependencies.
class Context
{
ICallManager* m_pCallManager;
public:
Context(ICallManager* pCallManager);
...
};
There has to be a place where I create CallManager like this
ICallManager callManager = new CallManager();
I read about composition root, but I don't like this approach because it breaks encapsulation and there are too many classes and the hierarchy is to deep to inject everything.
So far I am thinking of two solutions:
- To leave some members as is and rely on unit tests of that member's class. Doesn't work well for 3rd party libs/timers, etc.
class Context
{
CallManager* m_pCallManager;
public:
Context()
{
m_pCallManager = new CallManager();
}
// We assume that CallManager doesn't have bugs
};
- To use some kind of factory with enum for types. It's like composition root, but I don't create all objects and just give enum variable which type to use.
enum class Types
{
PROD = 1,
TEST = 2
};
struct Factory
{
static ICallManager createCallManager(Types type)
{
switch(type)
{
case PROD: return new CallManager(); break;
case TEST: return new CallManagerMock(); break;
}
}
};
class Context
{
ICallManager* m_pCallManager;
public:
Context(Types type)
{
m_pCallManager = Factory::createCallManager(type);
}
...
};
void main()
{
Context c = new Context(Types::PROD);
}
Is there a good compromise between encapsulation, testability and maintainability?