Reputation: 947
I have many, many modules that can benefit from using the exact same design pattern and share common classes. On the individual component level, everything makes sense, classes can easily be extended. But when I try to tie them together into a common module object, it seems like a pattern that polymorphism wasn't meant for, and I am missing the right pattern or design.
Starting out with all the base classes, which all other classes will extend from. The Module
is the glue and the problem. Module
will contain methods that prevent code duplication, such as AddComponent
.
// A physical interface (Ethernet, Bluetooth, etc)
class Interface {};
// A basic component
class Component {};
// An std::map wrapper for managing Components
class ComponentManager {};
// A place to store data
class Database {};
// A module to tie all things together
class Module {
public:
Interface interface;
ComponentManager manager;
Database db;
void AddComponent(Component& c) {
manager.AddComponent(c);
db.InsertComponent(c);
}
};
Everything is fine until we want to extend all or most of the classes and the Module
as well.
class EthInterface : public Interface {}; // cool
class UdpClientComponent : public Component {}; // cool
class UdpClientDatabase : public Database {}; // cool
//class UdpClientComponentManager : public ComponentManager {}; // 90% of the time won't need it
class UdpClientModule : public Module {
public:
EthInterface interface; // how to get an EthInterface instead of Interface?
UdpClientDatabase db; // how to get a UdpClientDatabase instead of Database?
};
I am trying to understand what pattern or design or what to use here. I think templates might not be the right solution because I've simplified this example, and don't think templates with 5 or 6 T
s are good design. I don't really get how to design this using ptrs because then I am feeding the extended Module
ptrs from the outside, and I want this to be self contained, so that people can just write UdpClientModule module
and they get batteries included, so to speak.
Upvotes: 0
Views: 408
Reputation: 2364
Interfaces depends from abstraction, you want to depend from concrete types and there where your design flaw. Keep the interface to do the "interface" and leave the concrete classes dealing with the concrete types.
class Module {
public:
virtual ~Module() = default;
void AddComponent(Component& c) {
manager().AddComponent(c);
db().InsertComponent(c);
}
virtual Interface& interface() = 0;
virtual ComponentManager& manager() = 0;
virtual Database& db() = 0;
};
class UdpClientModule : public Module {
public:
Interface& interface() override { return ethInterface; }
ComponentManager& manager() override { return udcClienddb; }
Database& db() override { return manager; }
void specialUdpMethod() const { /*...*/}
private:
EthInterface ethInterface;
UdpClientDatabase udpClientdb;
ComponentManager manager;
};
In this case you're stating every module must provide an interface, e component manager and a db. If you have more relaxed constraint you could move the dependencies to a dependency injection solution and use pointer instead.
Upvotes: 1
Reputation: 622
This might be a kick in the dark, but maybe it will send you searching in a different direction... You could use templates, redefining Module
to look something like this:
template <class IFC, class COMP, class CM, class DB> class Module {
public:
IFC interface;
CM manager;
DB db;
void AddComponent(COMP& c) {
manager.AddComponent(c);
db.InsertComponent(c);
}
};
But if you go that way you should make sure that IFC
, COMP
, CM
and DB
are derived from Interface
, Component
, ComponentManager
and Database
and for that you need concepts. I don't know about you, but that is a bit over my head, so I would go a different way:
class Module {
public:
Module(Interface &ifc, Database &_db) :
interface(ifc),
manager(), db(_db) {
}
void AddComponent(Component& c) {
manager.AddComponent(c);
db.InsertComponent(c);
}
private:
Interface &interface;
ComponentManager manager;
Database &db;
};
class UdpClientModule : public Module {
public:
UdpClientModule() :
Module(ethInterface, udpClientDb),
ethInterface(),
udpClientDb() {
}
private:
EthInterface ethInterface;
UdpClientDatabase udpClientdb;
};
It's still clumsy, but it at least gets you some of the way to where (I assume) you want to get.
Upvotes: 1