Template Method Pattern anstatt if-else?
-
Hallo,
bevor ich meine Frage stelle:
* Ich habe einiges gelesen unter google und habe die Sufu benutzt,
allerdings verstehe ich das Prinzip immer und immer noch nicht.
* Ich bin mit Templates bisher erst seit kurzem in Kontakt gekommenEs geht um folgendes:
Wenn ich ein Programm habe, das auch korrekt läuft. Jetzt will ich eine Optimierung einbauen, z.B die Benutzung eines hash-tables. Dies will ich aber frei wählbar machen, also z.B über ein input-flag an und ausschaltbar machen.
Jetzt habe ich es bisher so gemacht, dass ich entweder alles in die Methode hinzugefügt habe und mit vielen if-else abgefragt habe.
Oder ich dupliziere den code der Methode und springe schon von Beginn nach nur einem if-else in die korrekte Submethode. Könnte man auch über ein switch-statement realisieren.
Für immer mehr Optimierungen führt das aber zu einem Wirrwarr.Jetzt habe ich zum ersten mal was "gehört", dass es irgendwie über Template Methods gehen soll bzw. Template-Method-Pattern. Dazu muss man irgendwie mit virtuall umgehen können, was ich bis jetzt nicht kann.
Es würde mir Helfen wenn mir jemand einen einfachen Pseudocode hinschreiben könnte der mittels der gerade besagten Funktionalität arbeitet. So sollte man angeblich nur einmal code schreiben müssen? Stimmt das?
Für eure geduldige Hilfe bin ich dankbar
-
Eins vorweg, das Template Method Pattern hat nichts mit C++ templates zu tun.
Aber ist vielleicht trotzdem das richtige für dich. Oder vielleicht auch nicht, schwer zu sagen ohne näheres zu wissen.
Wenn es verschiedene, orthogonale Punkte gibt, böte sich das Strategy Pattern vielleicht eher an.
-
hmm....
ok das mit den templates das seh ihc jetzt natürlich
hat damit nix zu tun.Vielleicht versuche ich mein vorhaben auf einem minimalbeispiel, das nur pseudocode ist und gar nichts macht. Es geht mir nur ums Prinzip:
// in der main: class1.my_algorithm(); //class1 void class1::my_algorithm(input_param) { if (input_param == true) b = berechne_b(); else b = 0; a = berechne_a(b); funktion_b(a); //.... }dieses könnte man natürlich auch über ein switch-statement schreiben oder so machen:
// in der main: if (input_param) class1.my_algorithm_a(); else class1.my_algorithm_b(); //class1 void class1::my_algorithm_a() { b = 0; a = berechne_a(b); funktion_b(a); //.... } void class1::my_algorithm_b() { b = berechne_b(); a = berechne_a(b); funktion_b(a); //.... }nur würde ich beides gerne vermeiden - geht das mit template methods?
Aber auch wenn ich das strategy pattern einsetze - irgendwo MUSS ja ein switch oder if-else statement rein oder nicht? Weil nur so kann ich ja Klassenspezifisch die Methode aufrufen....

-
Template Method Pattern:
class class1 { public: void my_algorithm() { b = berechne_b(); a = berechne_a(b); funktion_b(a); } protected: virtual int berechne_b() = 0; virtual int berechne_a() = 0; virtual void funktion_b() = 0; }; class class1_variante1 : public class1 { protected: virtual int berechne_b() { return 0; } // etc. }; class class1_variante2 : public class1 { virtual int berechne_b() { return berechnung_von_b(); } // etc. }; class1* make_class1( bool input_param ) { if (input_param) // (input_param == true) ist so sinnvoll wie (((((input_param == true) == true) ... ))) return new class1_variante1; else return new class1_variante2; };Strategy Pattern:
struct b_berechner { virtual int berechne() = 0; }; struct b_berechner_variante1 { virtual int berechne() { return 0; } }; struct b_berechner_variante2 { virtual int berechne() { return berechnung_von_b(); } }; class class1 { public: class1( b_berechner* b ) : b_berechner_(b) { } void my_algorithm() { b = b_berechner_->berechne(); a = berechne_a(b); funktion_b(a); } private: b_berechner* b_berechner_; }; class1* make_class1( bool input_param ) { if (input_param) return new class1(new b_berechner_variante1)); else return new class1(new b_berechner_variante2)); };
-
Danke vielmals ! Ich habe nun das Template Pattern fast eingebaut - nur ich bleibe an einer stelle irgendwie hängen.
Mein compiler-fehler lautet:
main.cpp:147: Fehler: Variable »o_class« kann nicht als vom abstrakten Typ »Class_Base<double>« deklariert werden Class_Base.h:46: Anmerkung: denn die folgenden virtuellen Funktionen sind rein innerhalb »Class_Base<double>«: Class_Base.h:81: Anmerkung: void Class_Base<T>::test() [with T = double]mein code sieht so aus:
//in der main: Class_Base<double> o_class; if ( a ) { o_class = C<double>(); } else { // kommt noch } o_class.my_algorithm();und die Klassen
#ifndef GUARD_C_H #define GUARD_C_H #include "Class_Base.h" #include <iostream> template <class T> class C : public Class_Base<T> { public: // C<T>() { }; // ~C<T>() { }; private: protected: virtual void test() {std::cout << "C" << std::endl;} }; #endifund meine Base-class:
#ifndef GUARD_Class_BASE_H #define GUARD_Class_BASE_H template <class T> class Class_Base { public: virtual ~Class_Base() {}; void my_Algorithm(); private: protected: virtual void test() = 0; }; #include "Class_Base.imp" #endifKann mir jemand sagen warum?
Sobald ich das = 0 wegnehme kommt die fehlermeldung:undefined reference to `Class_Base<double>::test()'EDIT:
Anscheinend (für mich neu) darf ich nicht die Abstrakte-Klasse initialisieren sondern nur die Klasse C. Dann gehts.
-
Mati schrieb:
...nur ich bleibe an einer stelle irgendwie hängen.
... Variable ... kann nicht als vom abstrakten Typ ... deklariert werden
EDIT:
Anscheinend (für mich neu) darf ich nicht die Abstrakte-Klasse initialisieren sondern nur die Klasse C. Dann gehts.Ja, damit liegst du richtig. Abstrakte Typen sind im wesentlichen nur Schnittstellenzusicherungen. Man kann nur Zeiger des abstrakten Basistyps verwenden um auf beliebige davon abgeleitete Klassen zu verweisen. Eine Initialisierung über den Typ wäre auch wenig sinnvoll, besitzt der doch keine Funktionalität.
Nur mittels des Zeigers kannst du auch einen Algorithmus zur Programmlaufzeit ändern. Mit einer "normalen" Variable bist du auf den Typ festgelegt.
cu André
-
Kann denn die Unterscheidungs-methode in der abstrakten Klasse untergebracht werden? Eher weniger oder ? Weil sonst gibts ja nen include-kreis oder so...?
class1* make_class1( bool input_param ) { if (input_param) return new class1(new b_berechner_variante1)); else return new class1(new b_berechner_variante2)); };
-
Doch, natürlich. Je nach dem was dir konkret vorschwebt ist das entweder sinnvoll oder kontraproduktiv.
-
Hmm naja, ich will einfach nur ein switch - statement einbauen um mir halt den Pointer zurückzugeben.
Also praktich diese Methode:
class1* make_class1( bool input_param ) { if (input_param) // (input_param == true) ist so sinnvoll wie (((((input_param == true) == true) ... ))) return new class1_variante1; else return new class1_variante2; };In die Abstrakte Klasse setzen.
Nur bekomme ich das so nicht hin.
Meine header sehen so aus:
//Class_Base //hier will ich die Header Class_Variante1 und Class_Variante2 einfügen aber // es will nicht. #include "Class_Variante1" #include "Class_Variante2" class Class_Base { public: Class_Base* get(int opt) { switch(opt) { case 0: return new Class_Variante1(); case 1: return new Class_Variante2(); } } } //Class_Variante1 #include "Class_Base" class Class_Variante1 : public Class_Base {...}; //Class_Variante2 #include "Class_Base" class Class_Variante2 : public Class_Base {...};
-
Die abgeleiteten Klassen können nicht definiert werden, bevor die Basisklasse bekannt ist. Für die Konstruktion eines Objektes brauchst du aber die vollständige Definition. Folglich kannst du die Erzeugungsmethode mit den new-Aufrufen nicht direkt im Klassenkörper definieren, sondern nur außerhalb (nachdem die abgeleiteten Klassen definiert wurden).
Praktisch also im cpp-File. Das ist auch von den Abhängigkeiten her sinnvoller.
-
Finix,
du sagst, das TMP habe nichts mit Templates zu tun und gehst dann dazu über, mit virtuellen Funktionen um Dich zu schmeißen. Da muss ich Einspruch erheben, denn das TMP lässt sich natürlich vorzüglich mit Templates umsetzen – und in C++ würde ich im Zweifelsfall immer diese Methode verwenden, statt virtueller Funktionen.
Dass diese Methode in einem Thread im C++-Forum nicht einmal erwähnt wird, finde ich schade.
-
Also mich würde brennend interessieren wie das über Templates realisierbar wäre Konrad. Könntest Du vielleicht den vorig geposteten Beispielcode (so schlimm er auch ist) evtl. über Templates ins TMP packen?
Und was ist der Grund warum über Templates das TMP "besser" ist als über virtuelle Funktionen ?
Praktisch also im cpp-File. Das ist auch von den Abhängigkeiten her sinnvoller.
wenn ich gleichzeitig mit Templates arbeite - ist das dan auch möglich?
Weil sowas in ne cpp-zu schreiben geht ja net:template <class T> Class_Base<T>* Switch_Algorithm<T>::Get_Algorithm(const int opt_level) { switch(opt_level) { case no_opt: return new C1<T>(); case opt_1: return new C2<T>(); } }Könnte es sein, dass ich hier nicht drumrumkomme die Get_Algorithm in ne eigene Klasse bzw. in eine andere Klasse zu packen?
Danke
-
das würde ich auch gerne sehen. ging es dem threadstarter nicht darum, zur laufzeit zu entscheiden, was verwendet werden soll. c++ templates werden doch zur compilezeit festgelegt, wie können die virtuelle funktionen ersetzen?
-
Zur obigen Frage mittels der vermeideung von virtuellen Funktionen habe ich folgenden Thread gefunden:
http://www.c-plusplus.net/forum/viewtopic-var-t-is-192539-and-view-is-next.htmlNur was da der vorteil sein soll weiß ich (noch) nicht. Evtl. sind die Kosten um virtuelle Funktionen aufzurufen etwas teurer bezüglich Zeit?
Ich hätte da noch eine Frage und
ich weiß jetzt nicht ob ich deswegen einen neuen Thread aufmachen soll, deswegen poste ich lieber hier weil es könnte sein dass es eher hier her gehört:ich würde gerne folgenden Code so "vereinfachen", dass das switch statement entfällt. Nun bin ich mir nicht sicher ob es überhaupt geht - evtl. irgendwie über virtuelle Funktionen oder so?
Matrix<double> *A_REAL = NULL; Matrix<COMPLEX> *A_COMPLEX = NULL; switch(o_rm.Get_Type(file)) { case real: { std::cout << "\t Input is REAL!" << std::endl; o_rm.Read_Matrix(file, &A_REAL); // Mache was mit A_REAL; delete A_REAL; break; } case complex: { std::cout << "\tInput is COMPLEX" << std::endl; o_rm.Read_Matrix(file, &A_COMPLEX); // mache was mit A_COMPLEX delete A_COMPLEX; break; }In Read_Matrix gibt es Template-Spezifikationen, die unterschiedlich vom Template, die Matrix unterschiedlich aufbauen. Braucht ihr noch mehr Informationen?
Ich kann also erst zur Laufzeit entscheiden welches Template benutzt wird...
Das ganze läuft in meiner main(), also in keiner Template Klasse
-
ja schrieb:
ging es dem threadstarter nicht darum, zur laufzeit zu entscheiden, was verwendet werden soll.
Das lese ich nirgendwo. Wenn das der Fall ist, sind Templates in der Tat nich geeignet.
Mati schrieb:
Und was ist der Grund warum über Templates das TMP "besser" ist als über virtuelle Funktionen ?
Erstmal, weil es der C++-ige Weg wäre (so arbeitet ja auch die STL, wo möglich). Außerdem ist die Methode wesentlich effizienter als die über virtuelle Funktionen, weil die Auflösung eben zur Compilezeit und nicht zur Laufzeit geschieht.
Der Code sähe prinzipiell genauso aus, nur dass die Funktionen nicht virtuell gemacht werden und die Ziel-Klasse parametrisiert wird.
struct Algorithm1 { void Method1() {} void Method2() {} } struct Alg2 { void Method1() {} void Method2() {} } void my_method<T>(T const& algorithm) { algorithm.Method1(); // … was anderes … algorithm.Method2(); } // Aufruf: if (flag) my_method(Algorithm1()); else my_method(Alg2());
-
hmm...wie viel Zeit kostet denn der aufruf virtueller funktionen im vergleich?
Ist das nur marginal oder doch "zu spüren".
Bei mir würde es nur einmal passieren.Ich schaffe es leider über Templates nicht so ganz:
template <class T> class Algorithm1 { public: void Method1() {} void Method2() {} }; template <class T> class Alg2 { public: void Method1() {} void Method2() {} }; template<class T> T* Algorithm::Get_Algorithm(const int opt_level) { switch(opt_level) { case 0: return new Algorithm<T>(); case 1: return new Alg2<T>(); } } Base<T>::my_method(T* algorithm) { algorithm->Method1(); // … was anderes … algorithm->Method2(); } // in der main: Algorithm<double> o_alg; Base<double> o_base; o_base.my_method(o_alg.Get_Algorithm(0));mein error:
Fehler: »C<double>*« kann nicht nach »double*« in return umgewandelt werden main.cpp:154: instantiated from hereSchon klar der feher aber wie kann ich denn den switch in eine eigene Klasse packen um von dort den richtigen klassentyp zu returnen?
-
Konrad Rudolph schrieb:
Finix,
du sagst, das TMP habe nichts mit Templates zu tun und gehst dann dazu über, mit virtuellen Funktionen um Dich zu schmeißen. Da muss ich Einspruch erheben, denn das TMP lässt sich natürlich vorzüglich mit Templates umsetzen – und in C++ würde ich im Zweifelsfall immer diese Methode verwenden, statt virtueller Funktionen.
Dass diese Methode in einem Thread im C++-Forum nicht einmal erwähnt wird, finde ich schade.
Hier nochmal zum Mitschreiben: das Template Method Pattern hat nichts mit Templates zu tun! Es geht darum dass TMP != Templates und vor allem auch TMP != Template Technik.
Und in C++ stehst du nun mal oft nicht vor dem "Zweifelsfall" sondern vor Limitierungen der Sprache - sollte ich mich täuschen, wäre ich dir sehr verbunden würdest du mir deine Variante, rein mit Templates, in diesem Fall ("über ein input-flag an und ausschaltbar"), vorstellen.
-
Konrad Rudolph schrieb:
ja schrieb:
ging es dem threadstarter nicht darum, zur laufzeit zu entscheiden, was verwendet werden soll.
Das lese ich nirgendwo. Wenn das der Fall ist, sind Templates in der Tat nich geeignet.
ich hätte es so verstanden, weil er sich über die vielen if-else beschwert hat.
@Mati
Für dein switch Problem könnte das State Pattern was sein.
-
Danke ja - aber um ehrlich zu sein bin ich mir (egal wieviel ich übers state pattern lese) nicht sicher obs gehen wird...ich brauch auch immer einen beispielcode...ich suche schon danach aber sinnvolle einfache C++ bespiele zum state pattern finde ich nicht - wäre nett wenn mir jemand helfen könnte.
-
Mati schrieb:
ich suche schon danach aber sinnvolle einfache C++ bespiele zum state pattern finde ich nicht - wäre nett wenn mir jemand helfen könnte.
Du kennst google?
Du weißt wie man Suchworte in google einsetzt?Such mal nach: State Pattern C++
Und wenn du da wirklich nichts findest (bezweifel ich wenn ich das Überblicke), dann kannst du gerne weiter fragen... (Hinweis: selbst mit state muster c++ und Einschränkung auf deutsche Seiten wird man fündig...)
cu André