An der Lösung für folgendes Design-Problem beiße ich mir jetzt schon länger die Zähne aus und fange langsam an, daran zu zweifeln, dass es eine (schöne) Lösung dafür gibt. Ich versuche es möglichst kurz und verständlich zu halten.
Eine meiner zu instanziierenden Klassen:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
class Class
{
public:
Class(Type type, Foo foo);
};
|
Interface, um Details über die Instanziierung zu kapseln:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class AbstractCreator
{
virtual ~AbstractCreator();
virtual Class* create( Type type, Foo foo ) const = 0;
};
class ConcreteCreator
{
public:
virtual Class* create( Type type, Foo foo ) const override
{
return new Class( type, foo );
}
};
|
In der Praxis wird das genutzt, um in Ableitungen der create-Methode runtime checks zu machen, ob die Instanziierung erlaubt ist.
Factory, die von clients instanziiert wird:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Factory
{
public:
Factory
{
// fill object creator map on construction
add( "ObjectName", new Creator<Class>)
}
Class* create( const std::string& name, Type type, Foo foo )
{
// fowards to map entry
return _map[name]->create( type, foo );
}
private:
std::map<std::string, Creator*> _map;
}
|
Client code:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
int main()
{
Factory f;
factory.create(name, type, foo);
}
|
Das schöne ist nun, dass die Klassen und Instanziierungsmethoden völlig transparent für den Client sind und er nicht z.B. schon seine Factory mit Parametertypen templaten muss. Dummerweise bin ich durch die rein abstrakte Methode aber immer fest an die Signatur mit zwei Parametern gebunden. Was aber z.B. wenn ich folgende Klasse einführen will:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
class ExtClass
{
public:
ExtClass(Type type, Foo foo, Param newParam);
};
|
Dann müsste ich schon damit anfangen, create zu überladen, und längerfristig schreit das nach variadic template arguments. Das Problem ist nur, ich kann eine Methode
|
C-/C++-Quelltext
|
1
2
|
template<typename... Args>
Class* create(Args... args);
|
nicht virtuell überladen. Was kann ich hier also tun? Vielleicht gibts es ja hier einen Template-Guru, der eine Idee dazu hat.
Die Lösungen, die ich bisher dazu gefunden habe, erfordern beide, dass man den AbstractCreator templaten muss, was ich gerne vermeiden würde. Alternativ bin ich auch für komplett neue Design-Vorschläge offen, mit denen sich das einfacher umsetzen ließe.