OO Ansatz für Strategie Spiel - Designfrage
-
Hallo Ooptimist,
schau dir doch einmal www.widelands.org an. Widelands ist ein Spiel, das Siedler II sehr sehr ähnlich ist. Das Spiel ist Opensource, du kannst dir also einmal den Quellcode herunterladen und ansehen wie dort das Klassendesign realisiert wurde.
Ciao
-
.filmor schrieb:
Nochmal, willst du dich wirklich genau an die Siedler II halten?.
Nein eigentlich nicht. Schongar nicht Siedler II, wenn schon das erste
Habe nur das Prinzip gemeint, Bauspiel mit Wirtschaftssystem und Transport etc. aber ohne Milität. Ich mags friedlich..filmor schrieb:
Dann gibt es keine Pure Receiver (außer Militärgebäude). Das ist das worauf ich beim letzten eingehen wollte.
Das Farm ein Pure Producer ist ist genauso falsch (eine Farm nimmt Wasser an), wie dass Storage ein Pure Receiver ist (Lagerhäuser musst du definitiv gesondert behandeln).Ok, habs nicht genau genug gelesen. Du meintest die Reciever vom Producer ableiten. So gehts dann natürlcih. In meinem System soll es aber reine Produzenten geben, die Farmen, denn Wasser regnets vom Himmel
Am Ende der Kette stehen Konsumenten. Die werd ich dann wohl nochmal extra machen und nur vom Observer ableiten..filmor schrieb:
Zusätzlich heißt ein Pattern benutzen nicht zwingend, dass du deine Klassen auch direkt von den Observable etc. ableiten musst (hast du auch nicht getan und richtig erkannt) Das gehört in die (richtige) Basisklasse, dann ergibt sich alles implizit aus der Vererbungshierarchie. Deshalb hast du eine (fast) lineare Vererbung und daher fast keinen Overhead. Um virtuelle Methoden wirst du eh nicht herumkommen.
Der sinnlose und unschöne Overhead ergibt sich erst, wenn du sowohl von Pure Receiver als auch Pure Producer ableitest (wie hier bei Mill/Bakery), such zu dem Thema mal nach dem "deadly diamond". Siehe auch mein letztes Posting.Den "deadly diamond" kenn ich, ist mir gar nicht aufgefallen in meinem "Diagramm". Gibt halt noch keine UML Tags für dieses Forum

-
Factory Observer Builder Product Subject Observer (Creator) | | | | | | | | | | | | Building | | | | | | | | | | | ---------------| | / \ | | | | \ | Producer | \ | | | | | | | | | | | | ----| |----- | | | | | | | | Fabricator | | | | | | | | | | | | | Farm* Builder ------------>Farm Mill/Bakery Consumer *needed for each spec. Building TypeAlso jetzt aber

-
Ooptimist schrieb:
.filmor schrieb:
Nochmal, willst du dich wirklich genau an die Siedler II halten?.
Nein eigentlich nicht. Schongar nicht Siedler II, wenn schon das erste
Habe nur das Prinzip gemeint, Bauspiel mit Wirtschaftssystem und Transport etc. aber ohne Milität. Ich mags friedlich.Hey, was ist gegen S2 einzuwenden? Außerdem unterscheidet sich das Regelwerk doch iirc nicht besonders ...
Ooptimist schrieb:
Ok, habs nicht genau genug gelesen. Du meintest die Reciever vom Producer ableiten. So gehts dann natürlcih. In meinem System soll es aber reine Produzenten geben, die Farmen, denn Wasser regnets vom Himmel
Am Ende der Kette stehen Konsumenten. Die werd ich dann wohl nochmal extra machen und nur vom Observer ableiten.Welche Konsumenten?! Ohne Militärgebäude sind nur die Lagerhäuser "Konsumenten".
Ooptimist schrieb:
Den "deadly diamond" kenn ich, ist mir gar nicht aufgefallen in meinem "Diagramm". Gibt halt noch keine UML Tags für dieses Forum

Man braucht kein UML, dass musst du spüren, junger Padawan ;). Im Ernst, wenn es zwei Vererbungspfade zur Basisklasse gibt hast du automatisch den "Diamanten des Todes" (klingt cooler ;)) und den gilt es imo zu vermeiden.
/edit Joa, das letzte Diagramm sieht gut aus. Übrigens, die Creator Objekte (bitte nicht Builder, dass ist mehrdeutig) kannst du schön in ein Template verfrachten.
-
Hehe, ganz ruhig. Ich hab die ganze Siedler Reihe bis 4 durchgezockt, inklusive allen Mission CDs. Das 5 ist mir zu weit weg vom Original und der Charme ging auch verloren. 2 ist sicher das beste, nach 1 natürlich. :p
Naja, die Konsumenten halt. Irgendjemand geht dann zum "Bäcker" und kauft sich ein "Brot" und "isst" das dann. ( Auf den Farmen wächst kein Wheat sondern Weed, aber pssst..
)Ich finds halt graphisch dargestellt viel übersichtlicher, machs bei mir aber immer auf Papier. Nix UML. Mit Ascii Art ist halt etwas unschön, aber Du bist ja draus gekommen

Jetzt ists aber besser oder?
-
[quote=".filmor]/edit Joa, das letzte Diagramm sieht gut aus. Übrigens, die Creator Objekte (bitte nicht Builder, dass ist mehrdeutig) kannst du schön in ein Template verfrachten.[/quote] Ah ja? Was müsste ich für Parameter haben? Doch nur den Gebäudetyp oder?
-
[quote=".filmor]/edit Joa, das letzte Diagramm sieht gut aus. Übrigens, die Creator Objekte (bitte nicht Builder, dass ist mehrdeutig) kannst du schön in ein Template verfrachten.[/quote] Ah ja? Was müsste ich für Parameter haben? Doch nur den Gebäudetyp oder?
-
template <typename T> struct generic_creator { T* operator () () { return new T; } };Das kannst du dann bei deiner Factory als Standardparameter nehmen:
template <typename Product> class Factory { public: // ... typedef boost::function0<Product*> creator_type; // ... template <typename T> void register_ (id_type id, creator_type f = generic_creator<T>()) { register_creator (id, f); } };register_creator ist dabei deine "normale" Registrierfunktion.
Undfac.register_<Farm> ("Farm");sieht doch nett aus und ist leicht in ein Makro zu fassen (auch wenn man damit sehr vorsichtig sein muss).
Du kannst diesen "generic_creator" und den creator_type natürlich noch abändern, so dass er Parameter entgegen nimmt (hier ist der einzig sinnvolle wohl der Bauplatz).
-
Ui! Danke wiedermal.
Hätt ich nicht erwartet dass Du mir gleich den Code servierst, super
Sieht wirklich nett aus

Hab mein Compiler grad nicht dabei, versuchs grad im Kopf einzubauen.
Ist id_type ein Typ von Boost, oder ist der zum Selberbasteln?
Was meinst Du mit "meine normale Registerfunktion"? Hab ich bis jetzt gar keine.
Meine Implementation sieht momentan etwa so aus: http://www.c-plusplus.net/forum/viewtopic-var-t-is-153908.html
-
Nuja, da hast du Creators und Products. Ich seh da keine Factory.
id_type sollte normalerweise ein typedef auf einen entsprechenden Template-Parameter sein, ist meistens ein int oder string. Ziel des Factory-Patterns in diesem Zusammenhang wäre ja so ein Aufruf:player.build ("Farm", location); // entspricht (nach ggf. nötigen Prüfungen) in etwa player._buildings.append (factory("Farm") (location)); // oder player._buildings.append (factory.create ("Farm", location));wobei _buildings z.B. vom Typ ptr_vector<Building> wäre. Damit so etwas funktioniert, musst du die Typen, die die Factory ausspuckt erst registrieren. Schau dir mal die Implementierung aus Loki an (lass dich nicht von den ganzen typedefs und Überladungen abschrecken, das wichtige ist relativ übersichtlich unten) und besorg dir vielleicht auch das zugehörige Buch.
-
Hmm, da hab ich wohl was nicht komplett verstanden. Das UML Diagramm auf Wikipedia und anderen Seiten hat eben nur 4 Klassen Creator und Product plus die konkreten Ableitungen.
Da weiss ich ja nun was ich zu lesen hab
Danke mal soweit, ich hab sicher bald die nächste Frage..
-
.filmor schrieb:
..Damit so etwas funktioniert, musst du die Typen, die die Factory ausspuckt erst registrieren. Schau dir mal die Implementierung aus Loki an (lass dich nicht von den ganzen typedefs und Überladungen abschrecken, das wichtige ist relativ übersichtlich unten) und besorg dir vielleicht auch das zugehörige Buch.
Puh abschrecken ist gut, es verwirrt auf jedenfall ganz schön. Ich kapiers irgendwie nicht. Kennst Du nicht eine andere gute Bsp. Implementierung?
Oder ev. kannst Du mir noch mal mit etwas Code helfen, zur Factory?
Wäre spitzenmässig!
-
Zeig was du hast. Jetzt bin ich zu Hause, da hab ich meine "eigene" Implementierung auch zur Hand

-
Eingentlich nur das hier: http://www.c-plusplus.net/forum/viewtopic-var-t-is-153908.html nur gibt create() jetzt nen Zeiger zurück plus eine zusätzliche ConcreteProductB und ConcreteCreatorB, und die abgeänderte Main():
vector<Creator*> creators; Creator* creatorA = new Concrete_Creator(); Creator* creatorB = new Concrete_CreatorB(); Creator* creatorC = new Concrete_Creator(); creators.push_back( creatorA ); creators.push_back( creatorB ); creators.push_back( creatorC ); for( vector<Creator*>::iterator it = creators.begin(); it != creators.end(); it++ ) { Product* product; product = (*it)->create(); product->say_hello(); delete product; } delete creatorA; delete creatorB; delete creatorC;ISt übrigends Factory Method, meintest Du ne Abstract Factory? Weil das blick ich noch gar nicht.
-
Aber hey, nur wenn Du magst. Ev. krieg ichs mit ner einfachen Erklärung selber hin. Nur steh ich grad aufm Schlauch, was das registrieren betrifft.
-
Offensichtlich gibt es verschiedene Meinungen darüber, was eine Factory ist ;). Naja, hier mal das, was ich immer verwenden (entspricht quasi der Loki-Version, aber ohne die Überladungen für die Konstruktorparameter:
// factory.hpp #ifndef YDONEUM_FACTORY_FACTORY_HPP #define YDONEUM_FACTORY_FACTORY_HPP #include <map> #include <boost/function/function0.hpp> #include "policies/DefaultErrorPolicy.hpp" namespace ydoneum { template < class AbstractProduct, typename IdentifierType, typename ProductCreator = boost::function0<AbstractProduct*>, template <typename, class> class ErrorPolicy = policies::factory::DefaultErrorPolicy > class factory : public ErrorPolicy<IdentifierType, AbstractProduct> { public: typedef std::map<IdentifierType, ProductCreator> MapType; bool register_product (const IdentifierType& id, ProductCreator creator) { return _map.insert (MapType::value_type (id, creator)); } bool unregister (const IdentifierType& id) { return _map.erase (id) == 1; } /// \todo create would be more useful, if it could handle constructor /// arguments AbstractProduct* create (const IdentifierType& id) const { typename MapType::const_iterator i = _map.find (id); if (i != _map.end ()) return (i->second) (); else return OnUnknownType (id); } protected: MapType _map; }; } #endif// DefaultErrorPolicy #ifndef YDONEUM_FACTORY_POLICIES_DEFAULT_ERROR_POLICY #define YDONEUM_FACTORY_POLICIES_DEFAULT_ERROR_POLICY #include <exception> namespace ydoneum { namespace policies { namespace factory { template <typename IdentifierType, class AbstractProduct> struct DefaultErrorPolicy { public: class Exception : public std::exception { public: Exception (const IdentifierType& id) : _id (id) {} virtual const char* what () { return "Unknown Type"; } const IdentifierType& getId () { return _id; } ~Exception () throw () {} private: IdentifierType _id; }; protected: AbstractProduct* OnUnknownType (const IdentifierType& id) const { throw Exception (id); return 0; } }; } } } #endifkA, ob das funktioniert, ich habs glaub ich noch nie verwendet, weil ich immer halt Parameter brauchte und die deckt das Ding halt nicht ab...
-
Hi
War gestern noch unterwegs..Danke für den Codeschnippsel.
Hab mir das jetzt mal in ruhe angeschaut, aber ich schnall nicht alles.
Das mit dem registrieren ist jetzt klar. Aber vor allem diese zwei Template Parametertypename IdentifierType, typename ProductCreator = boost::function0<AbstractProduct*>,kapier ich nicht. Der zweite ist ja mit default belegt. Muss ich da ein ()-Operator in die AbstractProduct Klasse einbauen für den Functoraufruf?
Und was käme bei Id.Type rein? Ein enum mit all meinen Concrete Products?Irgendiwe fehlt mir noch ein bisschen der Überblick des Gesamtaufbaus..
-
IdentifierType ist etwas, was das Produkt eindeutig identifiziert. Es kann praktisch alles sein, was einen definierten < und ==-Operator (für std::map) besitzt. Ein enum ist zum Beispiel in diesem Fall sinnvoll, weil die Anzahl der Gebäude bekannt ist und sich im Verlauf der Entwicklung kaum verändern wird.
Der ProductCreator ist praktisch dein Creator, er ist als Template-Parameter gehalten, damit man auch etwas anderes als boost::function (z.B. Loki::Functor) verwenden kann. Das es eine Standardzuweisung hat solltest du dich darum nicht kümmern, wenn du Boost hast. boost::function akzeptiert jeden Funktionstyp (auch Funktionsobjekte) mit der entsprechenden Signatur (nimmt keine Argumente und liefert einen Zeiger auf AbstractProduct zurück).
-
Hey Danke .filmor
Ist echt spitzenmääässig, habs nun endlich alles geschnallt und zum laufen gekriegt.
Auch den netten Aufruf per generischem Creator in der Form:factory.register_product < ProductA > ( "ProductA" );Werd ich nun alles mal zusammenwurschteln und das mit den Gebäuden angehen.
Big Thx
