问题
In the product I am working, one of very basic scenario is serialization of classes. Typically a class to be serialized calls serialization on its sub-component
e.g. if there is a class s.t. class A{B;C;D;} then A.Pack will call pack function on B,C,D.
Since there are many such classes, same pattern of code has to be duplicated over and over again. Is it possible to encapsulate this behavior in a pattern (possibly using templates and inheritance)
回答1:
The usual way of making a template do this is to use a type list:
#include <iostream>
// typelist definition
struct Empty {};
template < typename H, typename T = Empty >
struct Cons {
typedef H head;
typedef T tail;
};
// interfaces all items support
class IPack
{
public:
virtual void Pack() = 0;
};
// some packable items
class Fee : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fee\n";
}
};
class Fi : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fi\n";
}
};
class Fo : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fo\n";
}
};
class Fum : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fum\n";
}
};
// these two templates create a composite IPack from a list
// of the types of its parts
template <typename Types>
class PackList : public PackList<typename Types::tail>
{
protected:
typedef typename Types::head Item;
Item item;
public:
virtual void Pack() {
item.Pack();
PackList<typename Types::tail>::Pack();
}
};
template <>
class PackList<Empty> : public IPack
{
public:
virtual void Pack() {}
};
// FeeFiFoFum is a composite of four items
class FeeFiFoFum : public PackList<Cons<Fee,Cons<Fi,Cons<Fo,Cons<Fum> > > > >
{
};
// create a FeeFiFoFum and call pack on it, which calls pack on its parts
int main ()
{
FeeFiFoFum giant;
giant.Pack();
}
Proper implementations of composites created from type lists give you accessors for the members and so on, but this is enough to show how they works, and prints out that it packed Fee, Fi, Fo and Fum without specifying any behaviour.
回答2:
One possible design that would help accomplish this is to use the Composite pattern. Your Component (to borrow from the Wikipedia drawing) is Packable, which would implement a Template Method Pack() that can do something like so:
GetChildren();
for each child:
child.Pack()
PackImpl();
PackImpl() is a pure virtual method in Packable, and all classes that inherit implement it appropriately. GetChildren() would return an STL container (possibly empty), for iteration. It can be implemented in Packable, along with a private member collection to store the child objects. Basically, you then inherit all the classes from Packable, implement PackImpl(), and you're done.
Note that this will cause issues if your inheritance hierarchy depends on the child pieces being members directly. If you've approached the problem in terms of aggregation, this should work well.
回答3:
It's possible that the Visitor pattern may help.
http://en.wikipedia.org/wiki/Visitor_pattern
The idea of this is to separate the traversal logic (stepping through your objects) from the handling of each object. In this case, the per-object logic is serializing (encoding) a single object (or deserializing, of course). This should be fairly simple and minimally repetitive using normal OOP techniques.
Implementing the traversal and the Visitor-pattern specific code is annoying, but it's mostly boilerplate and should be a one-off thing.
回答4:
One commenter wrote:
If you mean "is there a way I can write a template to automatically call a method on each of my member variables?", then the answer is no...
My (slightly evil) counter to that is yes, if the method is the destructor...
#include <iostream>
using namespace std;
bool Enable = false;
template <typename T>
class DS : public T {
public:
~DS() {
if (Enable) T::Serialize();
}
};
class A {
protected:
void Serialize() { cout << "A" << endl; }
};
class B {
protected:
void Serialize() { cout << "B" << endl; }
};
typedef DS<A> DSA;
typedef DS<B> DSB;
class C {
protected:
void Serialize() { cout << "C" << endl; }
private:
DSA a;
DSB b;
};
typedef DS<C> DSC;
int
main()
{
DSC c;
{
DSC c_copy = c;
Enable = true;
}
Enable = false;
}
The output is in reverse order, so to reconstruct objects you'd have to parse the serialized data and push each completed object on a stack. Composite objects would then know how many children to pop off of the stack. Or, of course, the serialization could go to an intermediate structure.
Another intriguing idea would be to use this hack once at startup (create and destroy only one special object) where the callbacks from the destructors would create a data structure that described the original object.
I also note that implicit copy constructors have potential for similar abuse, and possible in forward order...
来源:https://stackoverflow.com/questions/4229748/pattern-for-delegation-to-sub-component