Eine Klasse die je nach "Parametrierung" auf richtige andere Klassen zugreift
-
Ich glaube du möchtest auf das Delegation-Pattern hinaus

-
das pattern was du da gepostet ahst sieht nicht nach delegation aus, sondern nach dem strategy pattern, und das wäre genau das was ich auch gesagt habe, nur dass halt policy based design speziell für c++ mit templates ist
delegation ist übrigens kein eigenes pattern sondern nur eine technik. eine klasse delegiert den aufruf einer funktion an eine andere klasse weiter
dadrauf basieren dann mehr oder weniger alle patterns, die komposition anwenden
-
@Zisko: Wieso könnte ein Springer nicht alleine existieren wenn er ein Sportler wäre?
-
Dobi schrieb:
@Zisko: Wieso könnte ein Springer nicht alleine existieren wenn er ein Sportler wäre?
Ich versuche mal ein anderes Beispiel:
Es gibt Autos, Busse, LKWs usw. Sie können alleine alleine Existieren.
Dazu gibt es ein "MeinGeschmack", dass enthält dann beispielsweise die Farbe Blau, und Lederausstattung als Parameter.
MeinGeschmack kann ein Auto, Bus oder LKW sein.
Darüber hinaus haben Autos, Busse, LKWs und MeinGeschmack die Methode fahren. MeinGeschmack ruft dann in seiner Methode fahren, die Methode fahren der anderen Fahrzeuge auf und macht sie noch schneller!

Von Design Patterns weiß ich, dass es sie gibt, habe mich jedoch noch nie so richtig mit ihnen beschäftigt. Das liegt hauptsächlich daran, dass ich noch nicht so gut programmieren kann, dass ich das Muster in die Praxis übertragen kann. Ich werde mich wohl doch damit beschäftigen müssen.
-
Ja, das ist ein anderes Beispiel, ein ganz anderes.

"MeinGeschmack kann ein Auto, Bus oder LKW sein."
Geschmäcker sind doch keine Autos. Auto, Bus und LKW sind höchstens alles Fahrzeuge.
Was du nun wirklich willst, hab zumindest ich noch nicht verstanden.
Hast du nun verschiedene Sportler oder unterschiedliche Geschmäcker, die sich auf diverse Fahrzeuge beziehen?
Edit: Klar, tatsächlich hast du etwas ganz anderes und das sollten nur vereinfachte Beispiele sein, aber im Schritt der gedanklichen Abstraktion scheint sich dazwischen irgendein Fehler eingeschlichen zu haben.
-
Dobi schrieb:
Edit: Klar, tatsächlich hast du etwas ganz anderes und das sollten nur vereinfachte Beispiele sein, aber im Schritt der gedanklichen Abstraktion scheint sich dazwischen irgendein Fehler eingeschlichen zu haben.
Hmm vielleicht sind Beispiele der falsche Weg und ich versuche es eher auf abstraktem Weg mein Problem zu erklären.
Ich habe 3 Klassen: ClasseA, ClasseB und ClasseC. Alle drei Klassen habe eine ähnliche Aufgabe, führen den Algorithmus jedoch unterschiedlich aus, was im Detail zu leicht unterschiedlichen Ergebnissen führt. Die Methode die den Algorithmus ausführt heißt
void process( float& input, float& output )Die Klassen können alle drei für sich allein gestellt existieren und funktionieren auch.
Zu diesen Klassen gibt es eine ClassErweiterung. ClassErweiterung besitzt ebenfalls eine
void process( float& input, float& output )diese Verwendet das Ergebnisse der process von ClassA, ClassB oder ClassC.
Letztendlich kann man sich das so vorstellen:
ClassA.process(x,y) macht y = x+x;
ClassB.process(x,y) macht y = x-x;
ClassC.process(x,y) macht y = x*x;ClassErweiterung.process(x,y) macht ClassA.process(x,y) und anschließend y = 2*y;
Derzeit ist es so, dass ich zu ClassA, ClassB und ClassC jeweils eine ClassErweiterungA, B, C habe, was sehr unschön ist, da sich die einzelnen Erweiterungen nur davon unterscheiden, dass sie die jeweilige process von ClassA, B, C aufrufen.
Schön wäre es, wenn ich eine ClassErweiterung habe, der ich sage "Du bist jetzt die Erweiterung von ClassA". Ich habe es derzeit so gelöst, dass in ClassErweiterung jeweils ein Objekt von ClassA, ClassB und ClassC als Member erzeugt wird und dann über switch/case entschieden wird, welche process aufgerufen wird. Aber das ist eine extrem hässliche Lösung.
-
Zisko schrieb:
PS: auch wenn es logischer wäre den umgekehrten Weg zu gehen und Springer und Laeufer von Sportler erben zu lassen, muss die Implementierung so erfolgen da der "Sportler" in meiner Anwendung nur eine Erweiterung vom Springer und Laeufer ist, da Springer und Laeufer auch alleine existieren können. Daher hinkt das Beispiel etwas

Das verstehe ich nicht und deine Beispiele machen da IMHO auch nichts deutlicher.
class Base { virtual void process( float& input, float& output ); } class ClassA : Base { void process( float& input, float& output ); } class ClassB : Base { void process( float& input, float& output ); } class ClassErweiterung { Base* bla; void process( float& input, float& output ) { bla->process( input, output ); output *= 2; } }Grob, smart Pointer oder sowas halt selber machen.
Dann können doch Springer & co 'alleine existieren' (was auch immer du damit meinst).
-
Leite die 3 Klassen von einer gemeinsamen Basis mit rein virtueller
process()-Methode ab. Die Erweiterung arbeitet dann mit der Basisklasse, z.B. überstd::unique_ptr.
-
Wenn ich das richtig verstehe, dann Funktioniert das so:
Ich erstelle ein Interface namens Fahrzeug und die drei Klassen PKW, LKW und Bus die das Interface beerben
Nun kann ich ein Fahrzeug erstellen und sage ihm irgendo "Du bist ein LKW!"
Darüber hinaus kann ich eine Klasse Namens MeinAuto erstellen, dass einen Pointer von Fahrzeug enthält. Irgendwo muss ich dann in MeinAuto dem Pointer sagen, dass er ein PWK, LKW oder Bus ist.
Ist das richtig so?
-
äh ich glaube nein, sieh dir doch noch mal das Beispiel von JockelX an.
Du hast eine abstrakte Basis-Klasse Fahrezeug, die kann im Prinzip nix, hat nur virtuelle Methoden (also etwa ein Interface in Java)
Deine konkreten Klassen Auto, LKW, Bus erben von dieser Basis-Klasse und müssen jeweils die virtuellen Methoden implementieren, jeder auf seine Art.
In deinem Programm instanzierst du dann halt das, was du haben willst, ein Auto, einen Lkw oder einen Bus.
Aber ich vermute, so ganz kann ich auch noch nicht folgen, worauf genau du hinaus willst. Du brauchst keinen Pointer auf irgendwas. Du erstellst einfach das, was du haben willst, halt Auto, Lkw oder Bus.
-
Hab mich grade mal hingesetzt und eure Tipps in einen Programmcode umgesetzt.
Ich weiß nicht ob ich es auch so gemacht habe, wie ihr es empfohlen habt, aber es tut was es soll. Es entspricht glaub ich einem Factory Pattern. Aber keine Ahnung ob es das Pattern 1:1 richtig umsetzt.
# include <iostream> class Integrator { public: virtual void process() = 0; }; class BackwardEuler : public Integrator { public: BackwardEuler() { std::cout << "BackwardEuler Konstruktor" << std::endl; } void process() { std::cout << "BackwardEuler process" << std::endl; } }; class ForwardEuler : public Integrator { public: ForwardEuler() { std::cout << "ForwardEuler Konstruktor" << std::endl; } void process() { std::cout << "ForwardEuler process" << std::endl; } }; class PIController { public: PIController() { } void process() { std::cout << "PIController process" << std::endl; integrator_->process(); } void initialize( int integrationMethod ) { switch( integrationMethod ) { case 1: integrator_ = new BackwardEuler(); break; case 2: integrator_ = new ForwardEuler(); break; } } private: Integrator *integrator_; }; int main() { Integrator *IntegratorBackward = new BackwardEuler(); Integrator *IntegratorForward = new ForwardEuler(); PIController *PIControllerBackward = new PIController(); PIController *PIControllerForward = new PIController(); PIControllerBackward->initialize( 1 ); PIControllerForward->initialize( 2 ); std::cout << std::endl; IntegratorBackward->process(); IntegratorForward->process(); PIControllerBackward->process(); PIControllerForward->process(); }Ausgabe ist:
BackwardEuler Konstruktor
ForwardEuler Konstruktor
BackwardEuler Konstruktor
ForwardEuler KonstruktorBackwardEuler process
ForwardEuler process
PIController process
BackwardEuler process
PIController process
ForwardEuler processDanke für eure Hilfe!

Kritik und Verbesserungsvorschläge sind trotzdem weiterhin gewünscht
-
Ja, dein PIController::initialize hat etwas factory-pattern-mäßiges. Noch eleganter fände ich es, wenn du das Erzeugen des Integrators auslagern würdest. So z.B.:
Integrator* CreateIntetrator(int Method);PIController könnte dann einen Integrator* im Konstruktor nehmen. Das hätte den Vorteil, dass du nicht aufpassen musst, eine Instanz davon erst mit initialize zu bedienen, bevor du process aufrufst.
Momentan würde dir folgendes um die Ohren fliegen:PIController c; c.process();integrator_ würde bei der Dereferenzierung ja noch einfach irgendwo hinzeigen.
Deine PIController-Instanzen in main musst du übrigens nicht mit new erzeugen.
PIController PIControllerBackward; PIController PIControllerForward;würde es auch tun.
Nach der zuerst genannten Änderung könnte es dann so aussehen:
PIController PIControllerBackward(CreateIntetrator(1)); PIController PIControllerForward(CreateIntetrator(2));Damit 1 und 2 nicht so ganz esoterische magic numbers sind, könntest du die zumindest als enum irgendwo hinlegen. Wenn du deine Integrator-Factory als Klasse machst, könnten die beispielsweise da rein.
Achso, dass du memleakst, weißt du, oder? Was du mit new erzeugst musst du auch mit delete wieder loswerden. Mit RAII (std::unique_ptr bietet sich hier an) könntest du das jedoch noch eleganter und sicherer haben.
Das composition pattern (das du zusätzlich zur factory als creation pattern benutzt) ist übrigens das von Skym0sh0 erwähnte strategy pattern.
Auch wenn es so jetzt schon funktioniert, empfehle ich dir, die Sachen trotzdem mal umzusetzen. Wenn du dazu noch nen kleinen Schubbs brauchst, sag Bescheid.

Edit: Oh, und mach den Destruktor deiner abstrakten Basisklasse da mal virtuell, sonst passieren unschöne Dinge beim "delete integrator_;".

-
Hier stand viel Mist
-
Zisko schrieb:
Hier stand viel Mist
Da du im neuen Thread ( http://www.c-plusplus.net/forum/307018 ) teilweise noch die gleichen Fehler gemacht hast, schubbse ich einfach mal unaufgefordert in die richtige Richtung.

Hier eine Version (mit RAII), bei der ich die Sachen, die ich vorgeschlagen haben, umgesetzt habe.#include <iostream> #include <memory> #include <stdexcept> #include <cassert> class Integrator { public: virtual ~Integrator() {} virtual void process() = 0; }; class BackwardEuler : public Integrator { public: BackwardEuler() { std::cout << "BackwardEuler Konstruktor" << std::endl; } ~BackwardEuler() { std::cout << "BackwardEuler Destruktor" << std::endl; } void process() { std::cout << "BackwardEuler process" << std::endl; } }; class ForwardEuler : public Integrator { public: ForwardEuler() { std::cout << "ForwardEuler Konstruktor" << std::endl; } ~ForwardEuler() { std::cout << "ForwardEuler Destruktor" << std::endl; } void process() { std::cout << "ForwardEuler process" << std::endl; } }; typedef std::unique_ptr<Integrator> IntegratorPtr; enum IntegrationMethod { EulerBW, EulerFW }; IntegratorPtr CreateIntegrator( IntegrationMethod method ) { switch( method ) { case EulerBW: return IntegratorPtr( new BackwardEuler() ); break; case EulerFW: return IntegratorPtr( new ForwardEuler() ); break; } throw( std::invalid_argument( "unknown integration method" ) ); return IntegratorPtr(); } class PIController { public: PIController( IntegratorPtr&& integratorPtr ) : integratorPtr_( std::move( integratorPtr ) ) { std::cout << "PIController Konstruktor" << std::endl; assert( integratorPtr_ ); } ~PIController() { std::cout << "PIController Destruktor" << std::endl; } void process() { std::cout << "PIController process" << std::endl; integratorPtr_->process(); } private: IntegratorPtr integratorPtr_; }; int main() { PIController PIControllerBackward( CreateIntegrator( EulerBW ) ); PIController PIControllerForward( CreateIntegrator( EulerFW ) ); PIControllerBackward.process(); PIControllerForward.process(); }Trotzdem empfehle ich dir, noch zu lernen, dass du ohne RAII deinen mit new erzeugten Kram selbst deleten musst. So sähe das dann in der main aus:
#include <iostream> #include <memory> #include <stdexcept> #include <cassert> class Integrator { public: virtual ~Integrator() {} virtual void process() = 0; }; class BackwardEuler : public Integrator { public: BackwardEuler() { std::cout << "BackwardEuler Konstruktor" << std::endl; } ~BackwardEuler() { std::cout << "BackwardEuler Destruktor" << std::endl; } void process() { std::cout << "BackwardEuler process" << std::endl; } }; class ForwardEuler : public Integrator { public: ForwardEuler() { std::cout << "ForwardEuler Konstruktor" << std::endl; } ~ForwardEuler() { std::cout << "ForwardEuler Destruktor" << std::endl; } void process() { std::cout << "ForwardEuler process" << std::endl; } }; enum IntegrationMethod { EulerBW, EulerFW }; Integrator* CreateIntegrator( IntegrationMethod method ) { switch( method ) { case EulerBW: return new BackwardEuler(); break; case EulerFW: return new ForwardEuler(); break; } throw( std::invalid_argument( "unknown integration method" ) ); return 0; } class PIController { public: PIController( Integrator* integratorPtr ) : integratorPtr_( integratorPtr ) { std::cout << "PIController Konstruktor" << std::endl; assert( integratorPtr_ ); } ~PIController() { std::cout << "PIController Destruktor" << std::endl; delete integratorPtr_; } void process() { std::cout << "PIController process" << std::endl; integratorPtr_->process(); } private: Integrator* integratorPtr_; }; int main() { PIController PIControllerBackward( CreateIntegrator( EulerBW ) ); PIController PIControllerForward( CreateIntegrator( EulerFW ) ); PIControllerBackward.process(); PIControllerForward.process(); }Damit du sehen kannst, dass genausoviele Destruktoren wie Konstruktoren aufgerufen werden (In deiner memleakenden Version war das nicht der Fall!), lasse ich die auch eine Ausgabe machen. Du könntest dir aber auch in deinem Debugger breakpoints setzen und so dem Programmfluss folgen.