Factory & Reflection

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2382
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Factory & Reflection

Beitrag von Jonathan »

Ein Problem im Zusammenhang mit http://zfx.info/viewtopic.php?f=7&t=794 aber es ist eigentlich ein getrenntes Thema.

Ich möchte meinen Entitys Komponenten hinzufügen können. Dafür muss ich ein Objekt einer Komponentenunterklasse erstellen, dessen Typ ich anhand eines Strings bestimme (damit ich das dynamisch zur Laufzeit machen kann). Jetzt ist die Frage, wie programmiere ich am besten eine Klasse, die einen String bekommt und das entsprechende Objekt erzeugt? (Die Klassen haben alle die selbe Oberklasse) Ich möchte natürlich möglichst einfach neue Klasse hinzufügen können, das sollte nicht mehr als eine Zeile benötigen.
Die einfachste Lösung wäre ein großes if/elseif aber das sieht hässlich aus und ich muss die Implementierung immer ändern.
Bis jetzt möchte ich das Prototypmuster benutzen. beim registrieren übergebe ich den Namen als String und ein Objekt der Klasse, beim erstellen suche ich den String aus einer map raus und kopiere das Objekt, das mit in der Map liegt und gebe das dann zurück. Ich kann mit einem Funktionsaufruf neue Klassen registrieren und muss nie die Implementierung meiner Factory ändern, was beides sehr schön ist, allerdings muss ich dafür sorgen, dass jede Komponentenklasse kopierbar ist.

Mein zweites Problem ist, dass sinnvolle bearbeiten von Komponenten im Editor. Ich würde gerne wxPropertyGrid benutzen, das Problem ist, dass ich dafür ja alle GUI Elemente erzugen müsste. Ferner will ich natürlich Komponenten laden und speichern können, das sind eine Menge simpler Funktionen, die ich eigentlich nicht selber schreiben will. Ich brauche als Reflection für C++.
Meine bisherige Idee ist, ein paar Makros zu definieren, die ich dann für jede Memberdeklaration benutze. Sie erstellen einerseits den Member mit dem normalen Namen, fügt aber andererseits dessen Adresse in eine Liste ein. So kann ich im Spiel ohne performanceverlus alle Member ganz normal nutzen, aber trotzdem über sie iterieren. Wie genau ich das mit der Liste jetzt mache, weiß ich allerdings noch nicht, man kann ja während der Deklaration nicht direkt Code ausführen, und void* Zeiger reichen sicherlich auch nicht aus.

Ich hätte also gerne Meinungen zu meiner Factory Planung und konkrete Vorschläge für Reflection.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8264
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Krishty »

Hi,
Jonathan hat geschrieben:beim registrieren übergebe ich den Namen als String und ein Objekt der Klasse, beim erstellen suche ich den String aus einer map raus und kopiere das Objekt, das mit in der Map liegt und gebe das dann zurück. Ich kann mit einem Funktionsaufruf neue Klassen registrieren und muss nie die Implementierung meiner Factory ändern, was beides sehr schön ist, allerdings muss ich dafür sorgen, dass jede Komponentenklasse kopierbar ist.
Das ist eigentlich schon ein schöner Ansatz. Statt in der Map Default-Objekte zu speichern, könntest du Zeiger auf die Funktionen speichern, die dir so ein Objekt erzeugen:

Code: Alles auswählen

// Schnittstelle
class IComponent;

// Factory
class CFactory {
public:
    // Typ eines Funktionszeigers auf eine Create-Funktion - gibt IComponent & zurück, wird als ComponentCreationFunction angesprochen und erwartet ein int als Parameter
    typedef IComponent & (*ComponentCreationFunction)(int);

    // Hier registrieren sich neue Komponenten-Implementierungen. Könnte private sein mit Friendship oder so, deine Entscheidung.
    void RegisterComponent(
        string ItsName,
        ComponentCreationFunction ItsCreationFunction
    ) {
        MyMap.insert(ItsName, ItsCreationFunction);
    }

    // Die Factory-Funktion. Interessante Frage: const oder nicht? Verändert sich der logische Zustand einer Fabrik, wenn sie etwas produziert hat?
    IComponent & CreateComponent(
        string ItsName,
        int ItsDummyParameter
    ) {
        if(MapIterator const ToComponent = MyMap.find(ItsName) != MyMap.end()) // Variablendeklaration in if – äußerst nützlich
            return (*ToComponent->second)(ItsDummyParameter); // ruft die hinterlegte Funktion auf und gibt ihr Ergebnis zurück
        else
            throw Blubb;
    }

}; // class CFactory


// Eine Implementierung
class CComponentA : public IComponent {
    ...
private:
    // Die Factory-Funktion dieser Implementierung
    static // darf natürlich kein this erwarten, sonst wird es auch mit den Funktionszeigern schwieriger
    CComponentA & Create(
        int DummyParameter
    ) {
        return *(new CComponentA(DummyParameter));
    }

public:

    // Konstruktor.
    explicit // weil er nur einen Parameter hat
    CComponentA(
        int DummyParameter // nur dazu da, damit du siehst, wie man mit Funktionszeigern und Parametern umgeht, falls du das brauchst
    ) … }

    // Trägt diese Klasse in der gegebenen Factory ein. Deine Entscheidung, wann und wo und wie.
    static // an keine Instanz gebunden
    void RegisterAt(
        CFactory & Factory
    ) {
        Factory.AddComponent("A", &Create); // Funktionszeiger hinterlegen – unnütze Information:
                                            // Das & vor Create kannst du auch weglassen. Falls die Zielfunktion ein Template ist, solltest du das sogar, weil
                                            // der Visual C++-Compiler einen Bug innehat und Template-Funktionszeiger nicht optimiert, wenn man ein & davor schreibt.
    }

}; // class CComponentA
Code ist hingehackt, darum keine Gewähr auf Korrektheit oder Vollständigkeit. Ansonsten aber sparsamer, schneller und vom Konzept her intuitiver als Default-Objekte.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2382
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Jonathan »

Was ein wenig unschön daran ist, ist dass die Objekte dann wissen müssen, dass sie von einer Fabrik erstellt werden. Bei der anderen Lösung müssen sie einfach nur kopierbar sein, und ich denke, wenn man ein Objekt, das nur default Werte hat, kopiert, sollte das nicht wirklich langsamer sein, als es neu zu erstellen.

Hat noch jemand Ideen bezüglich Reflection?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8264
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Krishty »

Jonathan hat geschrieben:Was ein wenig unschön daran ist, ist dass die Objekte dann wissen müssen, dass sie von einer Fabrik erstellt werden.
Kannst den Erzeugungs-Code natürlich auch von der Komponente ins globale Namespace verlagern, solange dort der Konstruktor verfügbar ist:

Code: Alles auswählen

template <
    typename CComponent
> IComponent & Create(
    int DummyParameter
) {
    return *(new CComponent(DummyParameter));
}

Factory.RegisterComponent("A", &Create<CComponentA>);
Theoretisch ließe sich das Template auch in die Factory verschieben, im Sinne von Factory.Register<CComponentA>("A").
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Factory & Reflection

Beitrag von odenter »

Jonathan hat geschrieben: Mein zweites Problem ist, dass sinnvolle bearbeiten von Komponenten im Editor. Ich würde gerne wxPropertyGrid benutzen, das Problem ist, dass ich dafür ja alle GUI Elemente erzugen müsste. Ferner will ich natürlich Komponenten laden und speichern können, das sind eine Menge simpler Funktionen, die ich eigentlich nicht selber schreiben will. Ich brauche als Reflection für C++.
Meine bisherige Idee ist, ein paar Makros zu definieren, die ich dann für jede Memberdeklaration benutze. Sie erstellen einerseits den Member mit dem normalen Namen, fügt aber andererseits dessen Adresse in eine Liste ein. So kann ich im Spiel ohne performanceverlus alle Member ganz normal nutzen, aber trotzdem über sie iterieren. Wie genau ich das mit der Liste jetzt mache, weiß ich allerdings noch nicht, man kann ja während der Deklaration nicht direkt Code ausführen, und void* Zeiger reichen sicherlich auch nicht aus.
Ist schon lange her das ich mit wxWindows (wxWidgets) was gemacht habe, ich meine mich aber dunkel daran zu erinnern das es da ne Menge Macros gab um zur Laufzeit Infos über die Objekte und Methoden zu bekommen.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2382
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Jonathan »

Also so ganz bekomme ich das noch nicht hin:

Code: Alles auswählen

#include <map>

template <class tClass> tClass* FactoryWorker()
{
	return new tClass();
}

template<class tBaseClass, typename tKey> class Factory
{
public:
	tBaseClass* Create(tKey Key)
	{
		return m_Workers[Key]();
	}
	void Register(tKey Key, BLA)
	{
		m_Workers.insert(Key, BLA);
	}
private:
	map <tKey, BLA> m_Workers;
};

Factory.Register("ClassA", &FactoryWorker<ClassA>);
ClassA* p=Factory.Create("ClassA");
Im Prinzip speicher ich in der Map Funktionszeiger die ich je nach Key aufrufe. Damit ich diese erstellungsfunktionen nicht selber schreiben muss, will ich das mit dem Template mahen, der Compiler müsste dann ja bei jedem Registeraufruf eine entsprechende Funktion mit dem Typ erstellen.

Allerdings kriege ich jetzt die Syntax nicht ganz hin, was muss ich jetzt statt dem BLA jeweils schreiben (2 mal in Register und in der map Deklaration)?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8264
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Krishty »

In den public-Teil der Factory gehört noch der Typ der Funktionszeiger:

Code: Alles auswählen

// Typ eines Funktionszeigers auf eine Create-Funktion - gibt tBaseClass * zurück, wird als ComponentCreationFunction angesprochen und erwartet keine Parameter
typedef tBaseClass * (*ComponentCreationFunction)();
Dann

Code: Alles auswählen

        void Register(tKey Key, ComponentCreationFunction Function)
        {
                m_Workers.insert(Key, Function);
        }
und

Code: Alles auswählen

        map <tKey, ComponentCreationFunction> m_Workers;
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2382
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Jonathan »

Ok, danke, es funktioniert jetzt soweit.
Doch hat noch jemand eine Idee, wie ich eine Variable Anzahl an Konstruktorparametern unterstützen kann, ohne für jede Anzahl die Klasse zu kopieren? Ich will erstmal, dass alle Konstruktoren gleich aufgerufen werden, d.h. die Create Methode bekommt immer die selben Parameter (in Bezug auf Anzahl und Typen), aber ich will halt verschiedene Factory Objekte haben können, die dann unterschiedliche Parameter verarbeiten können.
Das mit "unterschiedlich" ist in Bezug auf die jeweiligen Typen ja mit templates sehr einfach, aber in Bezug auf die Anzahl wüsste ich nicht, wie ich das schaffe, ohne die Factory Klasse für jede Anzahl zu kopieren und verändern.

Ich bin mir nichtmal sicher, ob sowas überhaupt geht, aber Fragen kann ja nicht schaden.


Hier mal der Code wies bisher ist (ist durch Fehlermeldungen ein wenig aufgebläht):

Code: Alles auswählen

///Factory with 1 arguments
template<class tBaseClass, typename tKey, typename t0> class Factory1
{
public:
	typedef tBaseClass* (*WorkerFun)(t0 v0);
	typedef std::map<tKey, WorkerFun> tMap;


	///Register a new class
	void Register(tKey Key, WorkerFun Fun)
	{ 
		tMap::iterator It=m_Workers.find(Key);
		if(It!=m_Workers.end())
		{
			stringstream s;
			s << Key << " is already registered";
			BOOST_THROW_EXCEPTION(std::exception(s.str().c_str()));
		}
		else
			m_Workers.insert(pair<tKey, WorkerFun>(Key, Fun));
	}

	///This functions builds the object for you
	tBaseClass* Create(tKey Key, t0 v0)
	{ 
		tMap::iterator It=m_Workers.find(Key);
		if(It==m_Workers.end())
			BOOST_THROW_EXCEPTION(std::exception(CreateNotFoundMessage(Key).c_str()));
		else
			return It->second(v0);
	}

	///This is a intern function to create the actual object
	template <class tClass> static tBaseClass* Worker(t0 v0)
	{ return new tClass(v0); }

private:
	tMap m_Workers;

	std::string CreateNotFoundMessage(tKey Key)
	{
		std::stringstream s;
		s << "Couldn't find Class " << Key << "\n"
			<< "Registered classes are: ";

		pair<tKey, WorkerFun> P;
		bool a=false;//we only want ", " between the elements
		foreach(P, m_Workers)
		{
			if(a)
				s << ", ";
			s << P.first;
			a=true;
		}

		return s.str();
	}
};


//Benutzung:
Factory1<WorldObject, string, SceneManager*> m_ObjectFactory;
m_ObjectFactory.Register("Lumberjack", m_ObjectFactory.Worker<WorldObject>);
WorldObject* NewHouse=m_ObjectFactory.Create("Lumberjackl", &m_SceneManager);
			
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8264
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Factory & Reflection

Beitrag von Krishty »

Jonathan hat geschrieben:Doch hat noch jemand eine Idee, wie ich eine Variable Anzahl an Konstruktorparametern unterstützen kann, ohne für jede Anzahl die Klasse zu kopieren?
Das wird wohl erst mit C++0x’s Variadic Templates und Perfect Forwarding gehen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten