C++ Design of Entity & Manager Abstraction
I'm working on some code to work with a set of events. These events can be persisted in different ways, based upon their concrete type. I currently have the iEvent interface abstracting the events, the iBackend interface for each of the ways they can be persisted, and a set of small interfaces (iFileEvent, iDBEvent, etc) for each backend that defines conversion methods to the serialisation representation required from the event itself. Each concrete event class implements iEvent and whatever combination of capability interfaces it supports.
This all works fine when de-serialising as this is performed by the backend implementations (file, db, etc), so they know what concrete event types to create from their existing contents, and thus can use the concrete event class's public methods to create them. I am however now at the point where I need to serialise new events, and have a bit of a quandary.
The problem is that I'm not providing the concrete type to each backend, as they only get given an iEvent due to the iBackend interface.
I can throw in a dynamic_cast, or I can just use a rtti comparison (or a cheaper version thereof) and a static_cast, but I'm not sure of there's a better method for capability-querying, and I don't see a clean way of solving this with any of those suggestions.
My specific case of开发者_开发知识库 desiring conversions between the concrete event type and the data structures used to persist them in each backend could be implemented by a set of methods in the backend for each type of event, but I don't like that at all...but a set of conversion decorators for each combination of class and backend smells like a nasty case of class explosion as well.
Seems pointless adding classes when all I need is the event classes to state they support the interface for each backend...which leads me to think that the casting options are the only way forward.
So in summary, I'm looking for advice on better solutions to this.
Here's some solution:
- keep the explosion of {file, db } * { Event1, Event2 }
- but instead of creating separate class for every combination, try to build classes which works for more than one combination.
Then your code just lists all combinations, but creates the following kind of structures for each combination:
struct Comb1 { File f; Event1 e; Impl1 i1; };
struct Comb2 { Db d; Event1 e2; Impl2 i3;};
struct Comb3 { File f; Event2 e; Impl1 i1; };
struct Comb4 { Db d; Event2 e; Impl3 i1; };
These just choose correct implementation into use.
Then build base classes for all classes:
struct Generic { FileDbBase *ptr; EventBase *base; ImplBase *impl; };
Then create a 2d array of generic, indexed by both { file, db } and { Event1, Event2 }. This structure allows you to reuse existing implementations. Add instances of Comb1, Comb2, Comb3, Comb4 to the 2d array.
精彩评论