Factory erweitern



  • class Factory :
    {
    
    public:
    	enum PHONE_FACTORIES
    	{
    		SAMSUNG,		
    		NOKIA
    	};	
    
    	static Factory* CreateFactory(PHONE_FACTORIES factory);
    };
    
    include "classA"
    include "classB"
    
    Factory* Factory::CreateFactory(PHONE_FACTORIES factory)
    {
    	if (factory == PHONE_FACTORIES::SAMSUNG)
    	{		
    		return new ClassA();
    	}
    	else if (factory == PHONE_FACTORIES::NOKIA)
    	{
    		return new ClassB ();
    	}
    }
    
    Client:
    
    Factory *instance = Factory::CreateFactory(Factory::SAMSUNG); 
    (evtl. Samsung vorher setzen).
    

    In einem Client sollen zunächst zwei mögliche Instanzen erzeugt werden,
    von ClassA oder von ClassB. Später sollen noch andere Klassen
    dazukommen, z.B. ClassC.
    Ich möchte vermeiden, dass Client und Factory erweitert werden müssen.
    Außerdem soll die Factory Klasse möglichst nicht die Header
    von ClassA und classB einbinden, da die Factory nachher nicht
    wieder angefasst werden darf.
    Gibt es hierfür potentielle Designs?



  • Dann mußt du die Factory so gestalten, daß sich die Clients bei ihr registrieren müssen und selber dann eine Funktion zum Erzeugen anbieten.

    Ich habe mal alte Sourcen von mir rausgesucht:

    // factory.h
    #ifndef FACTORY_H
    #define FACTORY_H
    
    #include <map>
    #include <utility>
    
    // pluggable factories
    
    template <class Object, class Param, class ID>
    class Factory
    {
    public:
    	virtual ~Factory();
    
    	static Object* newObject(Param param);
    
    protected:
    	Factory(ID id);
    
    	static ID GetID(Param param); // must be implemented separately
    
    	virtual Object* makeObject(Param param) const = 0;
    
    private:
    	typedef Factory<Object, Param, ID>* MakerPtr;
    	typedef std::map<ID, MakerPtr> MakerMap;
    
    	static MakerMap st_registry;
    };
    
    template <class Object, class Param, class ID>
    Object* Factory<Object, Param, ID>::newObject(Param param)
    {
    	ID id = GetID(param);
    	MakerMap::const_iterator it = st_registry.find(id);
    	return it != st_registry.end()? it->second->makeObject(param) : 0;
    };
    
    template <class Object, class Param, class ID>
    Factory<Object, Param, ID>::Factory(ID id)
    {
    	std::pair<MakerMap::iterator, bool> p;
    	p = st_registry.insert(std::make_pair(id, this));
    }
    
    template <class Object, class Param, class ID>
    Factory<Object, Param, ID>::~Factory()
    {
    }
    
    template <class Object, class Param, class ID>
    Factory<Object, Param, ID>::MakerMap Factory<Object, Param, ID>::st_registry;
    
    // maker
    
    template <class Object, class Base, class Param, class ID, ID id>
    class Maker : public Factory<Base, Param, ID>
    {
    public:
    	Maker() : Factory<Base, Param, ID>(id)
    	{}
    
    protected:
    	Object* makeObject(Param param) const
    	{
    		return new Object(param);
    	}
    
    private:
    	static const Maker registerThis;
    };
    
    template <class Object, class Base, class Param, class ID, ID id>
    const Maker<Object, Base, Param, ID, id> Maker<Object, Base, Param, ID, id>::registerThis;
    
    #endif
    
    // FactoryTest.cpp
    #include "factory.h"
    #include <iostream>
    
    class TestBase
    {
    };
    
    class Test1 : public TestBase
    {
    public:
    	Test1(std::istream& is)
    	{}
    };
    
    class Test2 : public TestBase
    {
    public:
    	Test2(std::istream& is)
    	{}
    };
    
    typedef Factory<TestBase, std::istream&, int> TestFactory;
    
    int TestFactory::GetID(std::istream& is)
    {
    	int id;
    	is >> id;
    	return id;
    }
    
    void FactoryTest()
    {
    	// instantiate maker classes
    	static Maker<Test1, TestBase, std::istream&, int, 1> maker1;
    	static Maker<Test2, TestBase, std::istream&, int, 2> maker2;
    
    	TestBase *base = TestFactory::newObject(std::cin);
    }
    

    Vllt. hilft dir das weiter?



  • @Th69 sagte in Factory erweitern:

    Vllt. hilft dir das weiter?

    Danke. Kannst Du noch die Klassen für Object, Param und ID senden?



  • Das sind keine konketen Klassen, sondern Template-Parameter.
    Und in der FactoryTest-Funktion siehst du doch ein Beispiel dafür.

    Hier lauffähiger Ideone-Code mit Testdaten (von stdin).
    Mußte nur ein paar Anpassungen machen (der Code war damals C98 mit dem MSVC) - es fehlten einige typename vor Templates sowie weitere kleinere Anpassungen.

    Der Code war für das Laden von Objekten aus Dateien (daher std::istream).



  • @Th69 sagte in Factory erweitern:

    Mußte nur ein paar Anpassungen machen

    Ok, dann ist nur die Frage, an welcher Stelle das registrieren:

    static Maker<Test1, TestBase, std::istream&, int, 1> maker1;
    

    am besten geschehen soll. An dieser Stelle müssen ja auch die Header von ClassA und ClassB (in meinem Fall) eingebunden werden.



  • Du kannst dir die Dinge ggf. auch viel einfacher machen - reicht nicht sogar eine Map von id -> Funktion, um Objekt zu erstellen?

    Also ich meine konkret:

    std::map<PHONE_MANUFACTURERS, std::unique_ptr<Phone>(*)()> registry;
    

    Dann kannst du mit

    registry[PHONE_MANUFACTURERS::SAMSUNG] = []() -> std::unique_ptr<Phone> { 
        return std::make_unique<SamsungPhone>();
    });
    

    registrieren. Also im Prinzip hat @Th69 das ja auch gemacht und dann in einer Klasse gespeichert. Mir gefällt nicht so sehr, dass dort alles statisch zusammengestellt wird. Ich stelle mir eher vor, dass ich dynamisch einen Typ registrieren kann.

    Allgemein könnte man sich ja auch eine SamsungFactory vorstellen, die im createInstance() eben immer Samsung-Telefone erzeugt. So stelle ich mir das eigentlich vor. Also dass du ggf. eine Funktion suchst, die dir abhängig vom Parameter eine SamsungFactory oder NokiaFactory erzeugt, die dann eine parameterlose createInstance-Methode() hat.

    Wie weit du das treiben willst, ist eine andere Frage.

    Was an deinem ursprünglichen Code seltsam ist, ist dass die Fabrik eine Fabrik returnt und classA und classB von Fabrik erben. Und dass dein enum PHONE_FACTORIES statt PHONE_MANUFACTURERS heißt. Oder sollen classA und classB sowas wie AppleFactory und HuaweiFactory sein?


Log in to reply