Ähnliche Klassen mit einer unterschiedlichen Funktionssignatur generieren? (templates?)
-
Hallo zusammen, folgendes Problem:
Ich habe in meiner Anwendung mehrfach Factories, die ein etwas spezielles Muster aufweisen:
//forward-decl class Product; //end forward-decl class ProductFactory { // ein paar Dinge, um aus der Factory ein Singleton zu machen..... public: /** Erzeugen des Produkts * Diese Funktion ist der Knackpunkt des Ganzen, da sie von Factory zu * Factory unterschiedlich ist: * @param selector bestimmt mit, welchen Typ das Produkt am Ende tatsächlich hat * @params arg1, arg2,... werden an den Konstruktor der erzeugten Klasse weitergereicht.. **/ Product* createProduct(ProductSelection_t selector, Arg_t1 arg1, Arg_t2 arg2); /** Zerstören des vorher von der Factory erzeugten Produkts */ void destroyProduct(Product* p) { delete p; } /** Gemeinsamkeit: die Factory kann ein "Plugin" bekommen, das die Erzeugung übernimmt. */ public: class ReplacementFactory { public: virtual Product* createProduct(ProductSelection_t selector, Arg_t1 arg1, Arg_t2 arg2) const = 0; protected: virtual ~ReplacementFactory() = 0 {}; }; /** Setzen des Plugins **/ ReplacementFactory const* setReplacementFactory(ReplacementFactory const* replacement); private: ReplacementFactory const* replacementFactory; }; Product* ProductFactory::createProduct(ProductSelection_t selector, Arg_t1 arg1, Arg_t2 arg2) { /* ein paar spezielle Behandlungen... */ if (replacementFactory) return replacementFactory->createProduct(selector, arg1, arg2); switch(selector) { //... erzeuge product hier. } }Mir kommt mein Design grade ziemlich seltsam vor - eine verdrehte Version des abstract factory pattern.
Mein Problem ist, dass ich das gleiche Pattern mit verschiedenen Produkt-typen an mehreren Stellen in meiner Anwendung benötige. Der Unterschied ist leider nur, dass die create-Methoden immer unterschiedliche Signaturen haben. Ansonsten ist das Verhalten immer ähnlich:
- Die Default-Implementierung liefert Objekte aus einem Set D1..Dn von Derivaten des Produkt-Typs
- Die Ersatz-Implementierung liefert Objekte aus einem Set E1..En von Typen, die jeweils vom jeweilgen D-Typ abgeleitet sind.
Am Wichtigsten sind für mich die Trennung der Default- und Ersatzimplementierung, d.h. die Default-Implementierung muss ohne jedes Wissen um Details der Ersatzimplementierung auskommen.
Hat jemand eine Idee, wie man das hinbekommen kann, ohne jedesmal (fast) den gleichen Code zu schreiben?
-
Eventuell die Argumente in einem
std::tuplezusammenfassen? Dann könntest du verschiedene Verhaltens-Klassen mit einemtypedefhaben, welches derCreate()-Signatur in der Form einesstd::tupleentspricht. Grobe Skizze, könnte man mit Variadic Templates und RValue-Referenzen tunen:#include <tuple> struct A { typedef std::tuple<int, const char*> Args; static A* Create(Args x); }; struct B { typedef std::tuple<double> Args; static B* Create(Args x); }; template <typename T> struct Factory { typedef typename T::Args Args; template <typename T1> static T* Create(T1 a1) { return T::Create(std::make_tuple(a1)); } template <typename T1, typename T2> static T* Create(T1 a1, T2 a2) { return T::Create(std::make_tuple(a1, a2)); } }; int main() { Factory<A>::Create(3, "xy"); Factory<B>::Create(2.5); }Du kannst aber auch die Argumente 1:1 an die einzelnen Klassen weitergeben;
std::tuplebringt vor allem was, wenn du innerhalb vonFactoryviel herumreichst und nicht jedes Mal alle möglichen Signaturen aufschreiben willst.Falls ich dein Problem falsch verstanden habe, könntest du es vielleicht an einem einfacheren Beispiel erklären?
-
Okay, werd ich mal etwas konkreter

-
Ich habe eine Schicht-Architektur, bei der die Fassadenklassen der einzelnen Schichten von Factories erzeugt werden. Die Erzeugung wird in der jeweils übergeordneten Schicht aufgerufen. Schicht A lässt sich also von einer B-Factory eine AbstractBFacade erzeugen. Schicht B eine AbstractCFacade usw.
-
Die Fassaden der verschiedenen Schichten haben verschiedene Parameter, die sie zu ihrer Erzeugung brauchen. Die B-Factory und die C-Factory haben also verschiedene Signaturen ihrer create-Funktionen.
-
Ich möchte die Factories ersetzen können, damit sie Ableitungen der Fassadenklassen produzieren. Da diese Ableitungen und auch die Factories, die sie erzeugen, nichts mit der "standard"-Implementierung zu tun haben sollen, ist es nicht möglich, die Erzeugung über Parameter in der "standard"-factory einzustellen:
Beispiel:
- Schicht A ruft eine SingletonBFactory auf, hinter der entweder eine DefaultBFactory oder eine MockBFactory hängt. Die MockBFactory ist nur für Tests interessant, soll in der eigentlichen Anwendung also nicht vorkommen, daher soll sie (und das wissen darum) unabhängig vom Rest sein. Zu dem Zweck würde die SingletonBFactory an eine AbstractBFactory delegieren. DefaultBFactory und MockBFactory sind ableitungen davon und können in der SingletonBFactory bei bedarf ausgetauscht werden.- DefaultBFactory erzeugt entweder eine von mehreren DefaultBFacaden oder eine LoggingBFacade (Decorator-Pattern, hinter der LoggingBFacade hängt eine DefaultBFacade)
- MockBFactory ist ein Mock-Objekt, das an die Stelle der DefaultBFactory gesetzt werden kann. Die Applikation selbst merkt nichts davon. Die Create-Methode liefert eine MockBFacade, die auch ein Mock-Objekt ist.Soweit so gut, das hab ich auch schon umgesetzt. Mein Problem ist jetzt, dass das gleiche Muster (SingletonFactory, AbstractFactory, Defaultfactory, MockFactory, DefaultFacaden, LoggingFacade und MockFacade) auf mehreren Schichten stattfindet, wo die Konstruktoren der Fassaden und damit die Create-Methoden der Factories unterschiedliche Signaturen haben.
Ich würde dabei nur ungern die create-Signaturen künstlich vereinheitlichen. Weiß jemand Rat?
-
-
ich würde mal vermuten, dass in deinem Fall das Folgende dein bester Freund ist:
http://www.boost.org/doc/libs/1_46_1/libs/preprocessor/doc/index.html
du musst die Klassen also als Preprozessormakro schreiben welches dann automatisch die richtigen Signaturen erzeugt.
-
Hört sich mir eher nach einem Fall für boost::factory an.
-
nurf schrieb:
Hört sich mir eher nach einem Fall für boost::factory an.
Mit boost.factory komme ich da leider nicht weiter. Zumindest sehe ich nicht, wie.
-
pumuckl schrieb:
Mit boost.factory komme ich da leider nicht weiter. Zumindest sehe ich nicht, wie.
Nach längerem Tüfteln bin ich auf sowas gekommen (noch nicht ausgereift):
template <class AbstractFactory, class ManagementPolicy> class FactoryProvider : public ManagementPolicy { std::unique_ptr<AbstractFactory*> myFactory; public: typedef typename std:tr1::result_of<AbstractFactory::create>::type result_t; //oder so ähnlich public: FactoryProvider& instance(); //singleton-instanz template <class... A> result_t create(A... args) { ManagementPolicy::pre_create(args); result_t newResult = myFactory->create(args); ManagementPolicy::post_create(newResult); return newResult; } void destroy(result_t r) { ManagementPolicy::pre_destroy(result_t); myFactory->destroy(result_t); ManagementPolicy::post_destroy(); //? } void replace(AbstractFactory* newFactory) { myFactory.reset(newFactory); } };Die Policy kann alles mögliche übernehmen, z.B. nichtstun oder Sicherstellung, dass immer nur ein Objekt des Produkt-Typs erzeugt wird usw.
Über boost könnte man sich noch die Singatur der create-Methode der abstrakten Fabrik besorgen (das geht, turtle mocks nutzt die Möglichkeit) und damit das variadic template ersetzen.
Alternativ könnte ich vielleicht auch an Stelle der AbstractFactory nur die Signatur der create-Methode vorgeben und ein boost::function Objekt mit der passenden Signatur speichern sowie einen Deleter.Hm... mir kommen mehr Ideen. Ich meld mich, wenn ich was ausgereifteres hab

-
Haesslicher Code ist haesslich.
-
Und wieder ein Problem, das es so nicht gegeben hätte, wenn man auf Singletons verzichtet hätte

-
hustbaer schrieb:
Und wieder ein Problem, das es so nicht gegeben hätte, wenn man auf Singletons verzichtet hätte

Hm. Soll ich die abstrakte Factory global machen? Welche Alternative gibts?
-
Wie wäre es mit eine Template-Fabrik mit definierbare Funktionssignatur:
template < typename CreatorSignature , typename IdentifierType> class GenericFactory { public: //CreatorSignature darf Signatur oder boost::function-Typ sein //defniert boost::function-Typ passend für die gewünschte Signatur typedef typename boost::mpl::if_<boost::is_function<CreatorSignature>, boost::function<CreatorSignature>, CreatorSignature>::type CreationFunctor; //Create-Funktor-Parametertyp typedef typename boost::call_traits<CreationFunctor>::param_type FunctorParam; //ID-Parametertyp typedef typename boost::call_traits<IdentifierType>::param_type IdentifierParam; //Ergebnistyp typedef typename CreationFunctor::result_type ProductType; bool registerCreator(IdentifierParam identifier, FunctorParam creator) { return creators.insert(std::make_pair(identifier, creator)).second != 0; } CreationFunctor const & create(IdentifierParam id) const { typename CreatorMap::const_iterator i = creators.find(id); if(i != creators.end()) { return i->second; } else { throw //bad id } } private: typedef std::map<IdentifierType, CreationFunctor> CreatorMap; CreatorMap creators; }; //... GenericFactory<BaseType * (int, SomeType const &), std::string> factory; factory.registerCreator("product_a", DerivedTypeA::create); factory.registerCreator("product_b", DerivedTypeB::create); // BaseType * obj = factory.create("product_a")(42, SomeType("You drive me crazy"));So sinngemäß?
-
Tachyon schrieb:
So sinngemäß?
Geht schon in die Richtung, danke! Ich bau mir heute abend mal was

-
pumuckl schrieb:
hustbaer schrieb:
Und wieder ein Problem, das es so nicht gegeben hätte, wenn man auf Singletons verzichtet hätte

Hm. Soll ich die abstrakte Factory global machen? Welche Alternative gibts?
Ob global oder als Singleton ist für mich in diesem Fall das selbe.
Fass die nötigen Factories (bzw. (Smart-)Pointer darauf) in einer Struktur zusammen, und reich diese als Parameter rum.In vielen Fällen kann man auch Dependency-Injection verwenden - weiss nicht ob das bei dir geht.
-
hustbaer schrieb:
reich diese als Parameter rum.
In vielen Fällen kann man auch Dependency-Injection verwenden - weiss nicht ob das bei dir geht.
Wobe Dependency Injection und Rumreichen von Fabriken (oder irgendwie gearteten Referenzen/Pointern darauf) in meinen Augen das gleiche ist. Und nein, geht auf der Ebene nicht. Ich versuche das schon weitgehend zu machen, allerdings muss ich an irgendeiner Stelle den Startpunkt haben, wo die Fabriken erzeugt werden, die ggf. rumgereicht werden. Und ich kann weder dem Client meines Frameworks zumuten, alle Fabriken reinzustopfen (zumal er die nicht kennen sollte), noch kann ich Schicht 1 zumuten, dass sie die Fabrik für Schicht N erzeugt/verändert und dann durch alle anderen Schichten durchschubst, bis sie dort ankommt, wo sie zu landen hat. Zumal sie ja auch nichts außer der Fassade für Schicht 2 kennen sollte - und eben einer Möglichkeit, wie sie die Fassade bekommen kann.
Das ist (leider) ein inhärentes Merkmal meiner Architektur: Die Schichten werden on demand von oben nach unten/außen nach innen aufgebaut, da die utneren/inneren Schichten nicht immer gebraucht werden und ggf. durch andere Implementierungen ausgetauscht werden müssen.
-
Ein erster Wurf, natürlich fehlt noch einiges:
#include <boost/function.hpp> #include <boost/function_types/result_type.hpp> namespace util { template <class Signature, class DefaultCreator> class GenericAbstractFactory { typedef typename boost::function_types::result_type< Signature >::type ResultType; typedef typename boost::function<Signature> Creator; Creator theCreator; public: GenericAbstractFactory() : theCreator(DefaultCreator()) , create(theCreator) {} public: void replace(Creator const& newCreator) { theCreator = newCreator; } Creator const& create; }; } //ns util struct fn { int operator()(bool b) { return b? 5: 10; } }; #include <iostream> using std::cout; int main() { util::GenericAbstractFactory<int(bool), fn> factory; cout << factory.create(false) << '\n'; //10 cout << factory.create(true) << '\n'; //5 factory.replace([&](bool b) -> int { return b ? 20 : 50; }); cout << factory.create(false) <<'\n'; // 50 cout << factory.create(true) <<'\n'; // 20 }
-
So, nachdem ich endlich mal wieder dazu gekommen bin...
so siehts jetzt aus:#include <boost/type_traits.hpp> #include <boost/static_assert.hpp> #include <boost/function.hpp> #include <boost/function_types/result_type.hpp> #include <boost/mpl/if.hpp> #include <boost/preprocessor/repetition.hpp> #ifndef UTIL_GENERICFACTORY_MAX_ARITY #define UTIL_GENERICFACTORY_MAX_ARITY 10 #endif #define EMPTY(n) BOOST_PP_EMPTY() #define TEMPLATE_HEADER(n) \ template <BOOST_PP_ENUM_PARAMS(n, class T)> #define DECLARE_PARAM(z, n, unused) \ T##n&& t##n #define MULTIPLY(n) \ BOOST_PP_REPEAT(n, FACTOR, ~) #define FACTOR(z, n, unused) \ BOOST_PP_IF(n, *, ) t##n #define FORWARD_PARAM(z, n, unused) \ std::forward<T##n>(t##n) //1) #define DECLARE_CREATE(z, n, unused) \ BOOST_PP_IF(n, TEMPLATE_HEADER, EMPTY)(n) \ ResultType create(BOOST_PP_ENUM(n, DECLARE_PARAM, ~)) \ { \ static_assert(n <= arity, "Too many arguments!"); \ static_assert(n>= arity, "Not enough arguments!"); \ ManagementPolicy::preCreation(); \ ResultType r = theCreator(BOOST_PP_ENUM(n, FORWARD_PARAM, ~)); \ ManagementPolicy::postCreation(r); \ return r; \ } \ namespace detail { struct NoManagemetPolicy { void preCreation() {} template <class T> void postCreation(T const&) {} template <class T> void preDestruction(T const&) {} void postDestruction() {} }; } template <class Signature, class DefaultCreator, class DefaultDeleter, class ManagementPolicy = detail::NoManagemetPolicy> class GenericAbstractFactory : public ManagementPolicy { typedef typename boost::function_types::result_type< Signature >::type ResultType; typedef typename boost::mpl::if_< typename boost::is_pointer<ResultType>::type, ResultType, typename boost::add_reference<ResultType>::type >::type DeleterArg; public: typedef Signature CreatorSignature; typedef void DeleterSignature(DeleterArg); typedef boost::function<CreatorSignature> Creator; typedef boost::function<DeleterSignature> Deleter; private: const static unsigned arity = Creator::arity; private: Creator theCreator; Deleter theDeleter; GenericAbstractFactory() : theCreator(DefaultCreator()) , theDeleter(DefaultDeleter()) {} public: static GenericAbstractFactory& instance() { static GenericAbstractFactory theFactory; return theFactory; } void replace(Creator const& newCreator) { theCreator = newCreator; } void replace(Creator const& newCreator, Deleter const& newDeleter) { theCreator = newCreator; theDeleter = newDeleter; } //2) BOOST_PP_REPEAT(BOOST_PP_INC(UTIL_GENERICFACTORY_MAX_ARITY), DECLARE_CREATE, ~) void destroy(DeleterArg r) { ManagementPolicy::preDestruction(r); theDeleter(r); ManagementPolicy::postDestruction(); } }; #undef FORWARD_PARAM #undef DECLARE_PARAM #undef TEMPLATE_HEADER #undef EMPTY #undef MULTIPLY #undef FACTOR #undef DECLARE_CREATEDas Makro 2) sorgt dafür, dass das Makro 1) Mit Argumenten 0..N aufgerufen wird, was wiederum jeweils ein Methodentemplate definiert. Beispiel: (Für n=2):
template <class T0, class T1> ResultType create(T0&& t0, T1&& t1) { static_assert(2 <= arity, "Too many arguments!"); static_assert(2 >= arity, "Not enough arguments!"); ManagementPolicy::preCreation(); ResultType r = theCreator(std::forward<T0>(t0), std::forward<T1>(t1)); ManagementPolicy::postCreation(r); return r; }Dabei sorgt das static_assert dafür, dass tatsächlich nur die create-Methode instantiiert werden kann, die genau so viele Argumente hat wie die Signatur.
Ein kleineres Problem ahbe ich jetzt noch:
Ich habe eine Factory, deren Signatur ein std::function Objekt enthält:typedef std::function<void(ServerEvent const&)> ServerEventCallback; enum ConnectionType; typedef util::GenericAbstractFactory< ClientCommunicator*(ServerEventCallback const&, ConnectionType), DefaultCommunicatorCreator, DefaultCommunicatorDeleter, SingleProductPolicy > CommunicatorFactory;Natürlich kann ich jetzt an Stelle eines ServerEventCallback Objektes alles übergeben, was konvertierbar ist, z.B.
struct MyCallback { void operator()(ServerEvent const&) {}; } cb; /* ... */ CommunicatorFactory::instance().create(cb, SOME_CONN_TYPE);Ein Problem bekomme ich allerdings, wenn ich einen einfachen Funktionspointer übergebe:
void dummyCallback(ServerEvent const&) {} /* ... */ CommunicatorFactory::instance().create(dummyCallback, SOME_CONN_TYPE);3>f:\cardfw\src\cpp\test\communication_tests\test_communicatorfactory.cpp(38): error C2664: 'ClientCommunicator *util::GenericAbstractFactory<Signature,DefaultCreator,DefaultDeleter,ManagementPolicy>::create<void(const ServerEvent&), ConnectionType_t::type>(T0 (__cdecl &&),T1 &&)' : cannot convert parameter 1 from 'void (__cdecl *)(const cardfw::communication::ServerEvent &)' to 'void (__cdecl &&)(const cardfw::communication::ServerEvent &)' 3> with 3> [ 3> Signature=ClientCommunicator *(const ServerEventCallback &, ConnectionType), 3> DefaultCreator=DefaultCommunicatorCreator, 3> DefaultDeleter=DefaultCommunicatorDeleter, 3> ManagementPolicy=SingleProductPolicy, 3> T0=void (const ServerEvent &), 3> T1=ConnectionType_t::type 3> ] 3> None of the functions with this name in scope match the target type 3>Ich kann jetzt natürlich den funktionszeiger explizit auf ServerEventCallback casten (was ich auch mache), allerdings ist das lästig. Kann man das irgendwie umgehen?
-
pumuckl schrieb:
#define FORWARD_PARAM(z, n, unused) \ std::forward<T##n>(t##n) [...] #undef FROWARD_PARAMTippfehler.
-
Michael E. schrieb:
Tippfehler.
Danke, habs korrigiert.