Auf Interface Programmieren



  • CodeBase schrieb:

    Kann mir das einer erklären ?

    http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern

    Irgendwie fühle ich mich gerade ein bisschen erschlagen und weiß gerade nicht was ich mir den jetzt genau anschauen soll und was nicht. Was gilt für C++ ? Was nicht ? Wo bekomme ich Pattern her die ich brauchen kann und wo werden die Beschrieben (vll. auch in Deutsch).

    Mh, ich würd mir über die Zeit einfach mal alle Patterns angucken, dass du sie grundlegend verstehst (vllt mal implementieren) und weisst wofür und wann man die braucht.
    Und irgendwann kommst du dann an nen Punkt, wo du was machen musst und dir kommt in den Kopf "Ach Moment, da gabs ja was" und dann suchste dir das entsprechende Pattern raus, liest es nochmal durch und nutzt es...



  • krümelkacker schrieb:

    CodeBase schrieb:

    ich habe mal eine Frage. Ich habe mal wo gelesen das man immer auf ein Imterface Programmieren soll und nicht auf eine Implementierung.

    Jain. Gerade in C++ hast du mehrere Möglichkeiten dieser Art von Abstraktion. Man ist nicht auf Vererbung und virtuelle Funktionen beschränkt. Die Alternative heißt "generische Programmierung". Ich würde letzteres Vorziehen, weil es bzgl Performance kostenlos ist. Bei Gelegenheit kann man dann immer noch per Type-Erasure eine Art Laufzeitpolymorphie erhalten, beispielsweise Funktoren, die in einem std::function<>-Objekt drinstecken.

    Ich habe als Java-nach-C++ Umsteiger auch anfangs solche abstrakten Klassen geschrieben, weil es der einzige Abstraktionsmechanismus war, der mir vertraut war. Das ist allerdings 5 Jahre her. Und heute schreibe ich ganz anderen Code. new/delete/virtual sind Dinge, die ich höchst selten verwende.

    Heißt das, das Generische Programmierung das A und O heute ist ? Also nichts mehr mit new, pointern usw. ? Bin gerade etwas platt.



  • Doch Pointer schon, aber nicht mehr die regulären.
    Smart Pointer ist das Stichwort.



  • Nathan schrieb:

    Doch Pointer schon, aber nicht mehr die regulären.
    Smart Pointer ist das Stichwort.

    auto_ptr, shared_ptr usw. ?



  • Ja.
    Wobei eher unique_ptr.



  • Kann mir mal einer Zeigen, wie das Bsp. das ich gebracht habe (am anfang mit dem Managern) in der Generischen Programmierung umsetzten würde ?



  • CodeBase schrieb:

    Heißt das, das Generische Programmierung das A und O heute ist ? Also nichts mehr mit new, pointern usw. ? Bin gerade etwas platt.

    Ich denke nicht, dass das ein(e) erfahrene(r) C++ Programmierer(in) ernsthaft so behaupten kann. Stell Dir einfach C++ als eine verhältnismäßig große Werkzeugkiste vor. Da sind Werkzeuge drin, die dir bekannt vorkommen und welche, mit denen du im Moment noch kaum etwas anzufangen weißt. Das ist aber normal. Man kann nicht erwarten, dass Anfänger immer das "richtige Mittel" verwenden, um ein Problem zu lösen. Es braucht seine Zeit, um die Sprache in seiner Weite zu erfassen. Und es ist wohl auch normal, dass man von neu erlernten Dinge so begeistert ist, dass man die für alles Mögliche einsetzen will. Ich sage nicht, dass abstrakte Klassen und virtuelle Funktionen böse sind und heute nicht mehr benutzt werden sollten. Ich denke nur, dass man gerade als Umsteiger von Java solche Dinge eher überbeansprucht. Natürlich haben abstrakte Klassen und virtuelle Funktionen ihre Einsatzzwecke. Es gibt aber auch vieles, was man nicht in eine Klassenhierarchie zwängen und mit virtuellen Funktionen versehen muss.

    Bzgl Deiner PM: Ich weiß gar nicht, welches Problem du mit diesen Klassen lösen willst. Von daher kann ich nur allgemein bleiben und nicht wirklich andere Vorschläge machen.



  • Danke auf jeden fall für die Antwort.

    Was ich mit dem Problem lösen will ? Hmm eigentlich kein Konkretes, mir geht es um das verständnis. Ich will einfach lernen 🙂



  • Kann mir mal einer Zeigen, wie das Bsp. das ich gebracht habe (am anfang mit dem Managern) in der Generischen Programmierung umsetzten würde ?

    Immer wenn ich Klassennamen sehe wie "TextureManager", "ObjectManager" oder Command.execute() sehe, vermute ich vom Design her startke Maengel. Warum? Erfahrung! Es gab meist einen Weg, es treffender, einfacher, arbeitssparender und generischer zu loesen.

    Auch ist aus Meiner Sicht Vererbung eher hinderlich bei der Wiederverwendbarkeit von Klassen. Wenn sie in einem anderen Projekt benutzt werden soll, muss man den ganzen Rattenschwanz (oder gleich das ganze Framework) mitschleppen. Nachtraeglich kann eine Klasse immer in eine Vererbungshierarchie durch Adapter eingebracht werden. Herausnehmen ist ungemein schwieriger. Zwar sieht es total toll in Form von UML-Diagrammen aus, aber ist unglaublich unpraktisch. Nichtsdestotrotz hat Vererbung natuerlich Sinn, damit koennen beispielsweise gut Window-Toolkits aufgebaut werden. Ich kenne keines, dass nicht eine Basisklasse a la "Widget" hat, von denen abgeleitet wird.

    Und bei Java faellt mir immer Execution in the Kingdom of Nouns ein. Auch zitiere ich gern:

    Peter Norvig schrieb:

    Study of the Design Patterns book: 16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern

    Generische Programmierung das A und O heute ist ? Also nichts mehr mit new, pointern usw. ?

    Quatsch. Auch glaube ich, dass du eine falsche Vorstellung von generischer Programmierung hast.



  • knivil schrieb:

    Generische Programmierung das A und O heute ist ? Also nichts mehr mit new, pointern usw. ?

    Quatsch. Auch glaube ich, dass du eine falsche Vorstellung von generischer Programmierung hast.

    Das Glaube ich auch nur stehe ich gerade ein bisschen im Wald, weil das was ich bis jetzt als "richtig" angesehen habe, doch nicht richtig ist und man es anders löst. Bei mir fällt gerade alles zusammen weil ich einfach nimmer weiß, was jetzt richtig oder falsch ist. Bin nur im moment einfach ein bisschen gefrustet weil ich vererbung jetzt mal so richtig verstanden habe und jetzt stellt sich raus das es nicht richtig ist. 😕



  • Na ja, ist schwierig...

    Es ist vermutlich schon sinnvoll, wenn Du das Patternbuch erstmal in Ruhe durcharbeitest, denn Java kann man ja auch manchmal gebrauchen. Ich würde aber versuchen die Idee dahinter mehr zu verstehen und wo die ganze Sache jetzt eine Flexibilität hinzufügt, die man sonst nicht hätte.

    Wenn Du das Wissen hast, kannst Du damit natürlich auch schon C++ programmieren, aber da hast Du dann natürlich nur die halbe Wahrheit gelernt, weil Dir eben noch die besonderen C++-Mechanismen fehlen. Wenn Du C++ programmieren willst, solltest Du natürlich auch die Sprache beherrschen.

    Letztlich lernst Du so richtig sauberes Design nur mit viel, viel Zeit und Übung. Ich z.B. knabbere da auch noch dran und mache viele Designfehler, obwohl ich schon länger dabei bin (aber nicht besonders lange intensiv). Man muss die Dinge nicht perfekt machen (wenn es das überhaupt gibt) und Java-Patterns sind nicht direkt falsch, nur manchmal mehr oder wenig unelegant. Schau einfach, dass Du möglichst viel aus der Sache lernst, mache ein paar Projekte, diskutiere Lösungen für bestimmte Probleme etc. Du kannst nicht alles auf einmal lernen.



  • CodeBase schrieb:

    Bin nur im moment einfach ein bisschen gefrustet weil ich vererbung jetzt mal so richtig verstanden habe und jetzt stellt sich raus das es nicht richtig ist. 😕

    Es ist ja nicht falsch.
    Ich habe in C++ auch schon mal ein echtes Strategy Pattern mit Vererbung geschrieben und in einem Projekt genutzt. Es hat in dem Fall einfach gepasst.

    In anderen Fällen hingegen passt es nicht.

    CodeBase schrieb:

    Ich habe mal wo gelesen das man immer auf ein Imterface Programmieren soll und nicht auf eine Implementierung.

    Schon allein das "immer" macht diese Aussage falsch.

    Lies dein Buch durch und versteh es. Es ist wichtig, die Konzepte zu kennen, auch in C++. Dann musst du nur noch lernen, was es für Alternativen gibt und wann man die Alternativen anwendet.

    Stell dir das so vor, wie in dem Link von knivil (nur nicht so extrem). Ein Weg, etwas auszudrücken ist mit OOP und Klassen. Ein anderer ist generische Programmierung mit Templates (z.B. das Buch von Alexandrescu, obwohl das auch schon etwas veraltet ist). Noch ein anderer ist funktionale Programmierung ( != programmieren mit Funktionen ).

    C++ unterstützt viele Wege und einen guten Programmierer macht es aus, dass er erkennt, welcher für den jeweiligen Anwendungszweck angepasst ist.



  • Nochmal zum Originalpost:

    CodeBase schrieb:

    Ich habe mal wo gelesen das man immer auf ein Imterface Programmieren soll und nicht auf eine Implementierung.

    Eigentlich kann man den Satz ohne die Codebeispiele stehen lassen. "Interface" heißt für mich einfach nur Schnittstelle. Die Klasse std::string bietet dir ja auch eine bestimmte Schnittstelle an. Wie die genau intern funktioniert, ist ja nicht so wichtig. Man kann sie auch so verwenden, wenn man die Schnittstelle versteht. So gesehen, bezieht sich das eigentlich nur auf das "Geheimnisprinzip". Und dann wär ich damit einverstanden.

    Was du mit "Interface" meinst, ist eine abstrakte Klasse. Damit willst du eine Art Austauschbarkeit erreichen. Die bekommst du so auch. Sogar eine Austauschbarkeit zur Laufzeit. Das heißt beispielsweise: eine Funktion, die mit allen möglichen Objekten einer bestimmten Basisklasse klar kommen. Diese Art der Austauschbarkeit erfordert eine zusätzliche Indirektion. Alternativ bekommst du per Templates eine Austauschbarkeit, die auf die Compile-Zeit beschränkt ist. So oder so würde ich aber versuchen, "Overengineering" zu vermeiden. Es macht wenig Sinn, immer an solche Austauschbarkeiten zu denken, wenn du in 97% der Fälle später merkst, dass du das doch gar nicht nötig hattest.

    Was IMHO wichtig ist, dass man seine "Komponenten" einigermaßen entkoppelt bekommt, so dass das Design leichter anpassbar bleibt. Separation of concerns.



  • Vorschlag ... Ich vergess die generische mal fürs erste und mach mal weiter wie bis her



  • CodeBase schrieb:

    Kann mir mal einer Zeigen, wie das Bsp. das ich gebracht habe (am anfang mit dem Managern) in der Generischen Programmierung umsetzten würde ?

    Hallo CodeBase,

    dein Beispiel mit dem du den Thread begonnen hast, ist leider so unkonkret. Das macht Erklärungen immer schwierig und meiner Meinung nach auch schwerer verständlich. Deshalb hier mal ein konkretes Beispiel anhand der Container-Klassen und wie diese in Java bzw. C++ umgesetzt sind. Ich bin mir bewusst, dass ich dabei vereinfache und man dies weder in Java noch in C++ konkret so schreiben würde, es geht lediglich darum die Prinzipien zu veranschaulichen.

    Angenommen, ich möchte einen Container mit Werten füllen und anschließend ausgeben. Dabei möchte ich dem Prinzip folgen, gegen eine Schnittstelle zu programmieren und nicht gegen eine Implementierung. In diesem Fall bedeutet dies z.B., dass ich Code haben will, der mit einem beliebigen Containertyp zurecht kommt, egal ob ich ein Array oder eine verkettete Liste habe.

    In Java gibt es eine einheitliche Schnittstelle für alle Container, da alle Containter das Interface Collection implementieren. Da dir Vererbung schon bekannt ist, gehe ich nicht näher darauf ein, sondern liefere lediglich noch einen Link auf ein lauffähiges Beispiel. Der Code von fill und print ist unabhängig vom konkreten Containertyp, da die Funktionen allgemein ein Collection -Objekt annehmen.

    public static void fill(Collection c) {
      for (int i = 1; i <= 3; i++) {
        c.add(i);
      }
    }
    

    Dies ließe sich in C++ genau so umsetzen, da die Sprache die dafür benötigen Sprachmittel wie Vererbung und virtuelle Funktionen besitzt. Man hat dies allerdings aus Effizienzgründen in der Standardbibliothek von C++ anders gelöst. Alle Funktionen des Interfaces Collection sind nämlich virtuelle Funktionen, so dass bei jedem Aufruf von z.B. add der Typ des Containers bestimmt werden muss, damit dessen Implementierung der Funktion ausgeführt wird. Es gibt in C++ keine gemeinsame Basisklasse der einzelnen Container, sondern es ist implizit alles ein Container, wenn es die bestimmte Anforderungen erfüllt.
    Dies macht es nun möglich, generischen Code zu schreiben, der sich auf diese Anforderungen verlässt. Das vorherige Beispiel aus Java in C++ mit Templates umgesetzt, sähe also z.B. so aus. Da schon beim Kompilieren feststeht, welchen Typ das Objekt hat, kann direkt der richtige Code ausgewählt werden, ohne dass dies zur Laufzeit bestimmt werden muss.

    template<typename Container>
    void fill(Container &c) {
      for (int i = 1; i <= 3; i++) {
        c.push_back(i);
      }
    }
    

    Du kannst dir ja mal überlegen, welche Vor- und Nachteile es bei beiden Möglichkeiten (Vererbung vs. Templates) neben der Laufzeiteffizienz noch gibt. Da diese allerdings ein wesentlicher Grund ist, warum man C++ für eine Anwendung wählt, sieht man Templates hier eben häufiger als virtuelle Funktionen um generische Funktionalität zu erhalten.

    Ich hoffe, meine Erklärungen machen die Sache etwas klarer für dich.



  • Man hat dies allerdings aus Effizienzgründen in der Standardbibliothek von C++ anders gelöst.

    Nein, auch wenn es Effizienzgruende hat, waren sie nicht ausschlaggebend.

    template<typename Container>
    void fill(Container &c) {
      for (int i = 1; i <= 3; i++) {
        c.push_back(i);
      }
    }
    

    Nein, wo benutzt du in diesem Code Containereigenschaften aus http://en.cppreference.com/w/cpp/concept/Container ? Auch sehe ich nichts auf der Seite, was push_back fuer Container erzwingt. Und warumbenutzt du Iteratoren bei print aber nicht bei fill ? Und wenn du ein std::vectorstd::string mit fill benutzt? Und ... siehe std::fill ...



  • Sowas wie effizientes Arbeiten auf Sequenzen?





  • knivil schrieb:

    Nein, wo benutzt du in diesem Code Containereigenschaften aus http://en.cppreference.com/w/cpp/concept/Container ? Auch sehe ich nichts auf der Seite, was push_back fuer Container erzwingt. Und warumbenutzt du Iteratoren bei print aber nicht bei fill ? Und wenn du ein std::vectorstd::string mit fill benutzt? Und ... siehe std::fill ...

    Es ging mir im wesentlichen darum, an einem konkreten Beispiel zu zeigen, wie man in C++ anstatt Interfaces mit virtuellen Funktionen Templates verwenden kann um die gleiche Funktionalität zu erhalten. Dabei ging es lediglich um die Verwendung der Sprachmittel.
    Es ist richtig, dass man die Funktion in C++ so nicht schreiben würde und stattdessen eher ein Iteratorenpaar als Funktionsargumente verwenden würde. Dann wäre der Code aber nicht mehr direkt vergleichbar gewesen und deshalb habe ich auch keine std::fill verwendet.

    Bezüglich des push_back muss ich zugeben, dass ich in der Tat erst nach dem Schreiben bemerkt habe, dass das Container-Konzept in C++ keine Möglichkeit zum Einfügen garantiert. Ich habe dies einfach ignoriert und gehofft, es würde niemand bemerken. 🙂

    knivil schrieb:

    Man hat dies allerdings aus Effizienzgründen in der Standardbibliothek von C++ anders gelöst.

    Nein, auch wenn es Effizienzgruende hat, waren sie nicht ausschlaggebend.

    Ich bin nicht alt genug, um die Entstehung der STL mitverfolgt zu haben, meine aber dies in dieser Form gelesen zu haben. Kannst du kurz erklären, was sonst der Hauptgrund dafür war, keine Vererbungshierarchie für die Containerbibliothek zu nutzen?



  • ananas schrieb:

    Kannst du kurz erklären, was sonst der Hauptgrund dafür war, keine Vererbungshierarchie für die Containerbibliothek zu nutzen?

    Ich ziehe meine Frage zurück, nachdem ich mich jetzt selbst auf die Suche nach einer Antwort gemacht habe. Gefunden habe ich ein Interview von Alex Stepanov aus dem Jahr 1995.

    Alex Stepanov schrieb:

    Even now C++ inheritance is not of much use for generic programming. Let's discuss why. Many people have attempted to use inheritance to implement data structures and container classes. As we know now, there were few if any successful attempts. C++ inheritance, and the programming style associated with it are dramatically limited. It is impossible to implement a design which includes as trivial a thing as equality using it. If you start with a base class X at the root of your hierarchy and define a virtual equality operator on this class which takes an argument of the type X, then derive class Y from class X. What is the interface of the equality? It has equality which compares Y with X. Using animals as an example (OO people love animals), define mammal and derive giraffe from mammal. Then define a member function mate, where animal mates with animal and returns an animal. Then you derive giraffe from animal and, of course, it has a function mate where giraffe mates with animal and returns an animal. It's definitely not what you want. While mating may not be very important for C++ programmers, equality is. I do not know a single algorithm where equality of some kind is not used.

    You need templates to deal with such problems. You can have template class animal which has member function mate which takes animal and returns animal. When you instantiate giraffe, mate will do the right thing. The template is a more powerful mechanism in that respect.


Anmelden zum Antworten