Hallo Besucher, der Thread wurde 3,2k mal aufgerufen und enthält 18 Antworten

letzter Beitrag von Zirias/Excess am

Towers of Hanoi

  • Hi,


    ich möchte eine kleine Fingerübung zu meiner Konsolen-Anwendung zeigen. Dachte, das schaffe ich in ein paar Nachmittagen, aber durch den Karo-Hintergrund wurde das für mich etwas diffizil.



    Wer mal gucken möchte: ...
    towers_code C++.zip


    PS: wer eine andere Auflösung als 1080p hat, bei dem wird es wohl ein kleines Kuddelmuddel geben. Überhaupt habe ich gar keine Ahnung, ob der Code auf einem anderen Rechner läuft :huh:

  • Also ich kann bestätigen, dass es läuft!


    Habe gerade einen ersten Blick auf den Code geworfen. Schön finde ich, dass die tatsächlichen Console-Funktionen in einer Klasse abstrahiert sind -- ob es dafür unbedingt ein Singleton gebraucht hätte mal dahingestellt, das wird inzwischen ja eher als Antipattern angesehen -- vielleicht hätte hier eine statische Klasse genügt? Jedenfalls bietet das prinzipiell die Möglichkeit, den Code z.B. auf curses zu portieren, so dass es auch auf anderen Betriebssystemen läuft :)


    Mit der Struktur der Header tue ich mich etwas schwer -- Header, die ihrerseits andere Header includen finde ich unübersichtlich, besser ist IMHO, wenn jedes Implementierungsfile alles selbst included, was es braucht. Außerdem bin ich mir nicht sicher, ob jeder Compiler #pragma once unterstützt -- ist das im C++ Standard? Andernfalls tun es ja auch die "guten alten" guard Macros :)


    Auf jeden Fall nettes Progrämmchen! :)

  • ob es dafür unbedingt ein Singleton gebraucht hätte mal dahingestellt, das wird inzwischen ja eher als Antipattern angesehen -- vielleicht hätte hier eine statische Klasse genügt?

    Statische Klassen sind aus Sicht der Clean-Code-Fetischisten genauso böse wie Singletons. Wegen der Abhängigkeiten.
    Demnach müsste man konsequent IOC-Contrainer oder Class-Factories benutzen. Für einfach Projekte wohl etwas übertrieben.


    Solange man in in keinem agilen Entwicklungsteam arbeitet, kein Multithreading und keine Unit-Tests implementiert, geht das mit den Singletons völlig in Ordnung.
    Ich nutze die haufenweise in meinen (privaten) Projekten.


    Man muss nicht immer alles nachbeten, was die Clean-Code-Anhänger vorgeben. :D


    Ich habe mir jetzt den Quellcode nicht angeschaut, aber es ist nett animiert. 8)

  • Solange man in in keinem agilen Entwicklungsteam arbeitet

    Haha, also in meiner Bude wird versucht (Betonung darauf), "agil" zu entwickeln ;) Mein Chef in der Architektur-Abteilung ist allerdings recht angetan vom funktionalen Stil (finde ich auch cool) und da sind statische Funktionen ganz gerne gesehen -- Exceptions dafür gar nicht :)


    Hintergrund war bei meiner Anmerkung also eher, dass ein Singleton den statischen Charakter nur kaschiert -- eine statische Klasse ist da einfach "ehrlicher" ;)

  • Hintergrund war bei meiner Anmerkung also eher, dass ein Singleton den statischen Charakter nur kaschiert -- eine statische Klasse ist da einfach "ehrlicher"

    Naja, ein Singleton kann halt schon mehr ein statische Klasse. Z.B. gibt es einen Konstruktor, wo man Devices vor der ersten Benutzung zu initialisieren kann. In statischen Klassen braucht dafür wieder Zustandsflags.
    Aber genau das will man beim Unittesting gerade nicht haben.


    Ich nutze Singletons immer dann, wenn ich auf Resourcen zugreife, die ich auch nur einmal belegen kann. Sqlite-Datenbanken, Schnittstellen oder auch bei Daten, die einmal geladen und dann vorgehalten werden.

  • Houghouh, ein paar Antworten :) Danke.


    Ja, der Singleton. Ich weiß inzwischen, das das Ding alt ist und heute nicht mehr gern gesehen wird. Aber weil Console der Grundstock für diese Anwendung ist, wollte ich, das man ein Objekt davon nicht durch alle Funktionen durchschleppen muss. Da hilft auch das Argument unflexibel nicht, weil man, soweit ich weiß, eh nur eine Konsole pro Prozess öffnen kann. Aber in Wirklichkeit ist das eigentlich nicht auf meinem Mist gewachsen, sondern habe es mir abgeschaut. Bevor ich überhaupt wusste, was man mit "Pattern" meint.


    Und der Code ist ständig in Entwicklung. Was man oben runterladen kann ist schon wieder deprecated, wurde inzwischen geändert. Vor allem die Klasse DrawArea wurde verschlankt. Waren noch viele Altlasten drin.


    drawing_functions möchte ich zB gerne von DrawArea erben, aber Vererbung ist noch Neuland für mich. Versuche mich gerade dran, bin aber leider nicht die Schnellste. Ich habe noch ein paar mehr Klassen entwickelt, einen Paint-Bereich, eine Plasma-Klasse, 3D-Objekte, FIGlet-Fonts, ASCII-Arts. Für TowersOfHanoi sind nur die Header drin, die dafür benötigt werden.


    @zrs1 hat zB die Headerstruktur kritisiert, aber es gibt noch soviele Kritikpunkte an dem Code, wo ich manchmal gar nicht verstehe, wie ich das ändern kann. Es gibt noch so viel zu tun. Aber ich freue mich riesig über das Feedback :)

  • Naja, ein Singleton kann halt schon mehr ein statische Klasse. Z.B. gibt es einen Konstruktor, wo man Devices vor der ersten Benutzung zu initialisieren kann. In statischen Klassen braucht dafür wieder Zustandsflags.

    Auch das ist etwas, was ein Singleton nur (mehr oder weniger elegant) versteckt. Dein Zustandsflag ist einfach ein instance member. Je nach Sprache kann es so richtig "bekloppt" werden, ein Singleton thread-safe zu instantiieren ;) In anderen Sprache geht das zwar sehr einfach, liegt aber daran, dass die Komplexität vor dem Programmierer versteckt wird.

    Aber weil Console der Grundstock für diese Anwendung ist, wollte ich, das man ein Objekt davon nicht durch alle Funktionen durchschleppen muss.

    Die Frage ist eben: ist es überhaupt ein Objekt? Oder nicht eher etwas globaler Status und eine Reihe von Funktionen (also, eben eine statische Klasse)? Für solche Dinge nimmt man zumindest nach meiner Erfahrung eher statische Klassen, z.B. sind Logger oft so implementiert. Am besten natürlich wenn man dabei auf Status komplett verzichten kann -- keine Ahnung ob das bei deiner Konsolen-Abstraktion möglich ist, so genau hab ich nicht reingeschaut :)

    drawing_functions möchte ich zB gerne von DrawArea erben, aber Vererbung ist noch Neuland für mich.

    Vorsicht hier: Vererbung von Funktionalität ist eine "can of worms", in der Regel versucht man, das zu vermeiden. Es gilt der Grundsatz "composition over inheritance". Generell sollte Vererbung nur dann eingesetzt werden, wenn es eine echte "is-a relationship" gibt -- also klassisches Beispiel, ein PKW ist ein Fahrzeug, man könnte also einen PKW als Klasse implementieren, die von einem Fahrzeug erbt. Wenn das so nicht der Fall ist (und deine Namen hören sich nicht so an) -> Finger weg :) Rein vom Namen her hört sich drawing_functions überhaupt nicht nach einem Objekt im Sinn von OOP an, damit wäre es sowieso wieder ein Kandidat für eine statische Klasse. Falls es tatsächlich selbst ein Objekt ist, wäre es vermutlich schlauer, wenn es eine Referenz auf eine DrawArea hat, die es intern benutzt (composition eben).

    Aber ich freue mich riesig über das Feedback :)

    Freut mich :) Wie gesagt, schönes Projekt, viel Erfolg noch damit!

  • Danke für die Antworten :)


    wg #pragma once. Hier
    https://en.wikipedia.org/wiki/Pragma_once#Portability
    hat der einzige Compiler, der nicht #pragma once unterstützt, nichtmal einen Eintrag. Also denke ich mal, #pragma once wird grundsätzlich unterstützt.


    Bezüglich der Headerstruktur. Sollte ich statt das praktisch jeder header alle vorherigen inkludiert, lieber zB in der main() alle benötigten header inkludieren? Die einzelnen header inkludieren dann nur, was sie persönlich benötigen.

  • Also denke ich mal, #pragma once wird grundsätzlich unterstützt.

    Dein Link beantwortet aber auch meine Frage: Es ist auch in C++ nicht Teil des Standards. Kann man jetzt sehen wie man will, ich persönlich verwende nichts, was nicht Sprachstandard ist :)


    Bezüglich der Headerstruktur. Sollte ich statt das praktisch jeder header alle vorherigen inkludiert, lieber zB in der main() alle benötigten header inkludieren? Die einzelnen header inkludieren dann nur, was sie persönlich benötigen.

    IMHO ja. Das verbessert die Übersichtlichkeit (so wird z.B. in deinem main File Console verwendet, man würde dann direkt sehen, wo es "herkommt") -- und bei einem größeren Projekt verbessert es auch die Compile-Zeit, da sich der Compiler für eine Translation-Unit nicht durch viel irrelevanten Code "wühlen" muss und weil Abhängigkeiten weniger werden. Deshalb versucht man eigentlich auch, in Headern selbst mit forward declarations *) "durch die Tür zu kommen", bevor man ein #include einfügt :)


    ---


    *) Kleines Beispiel aus eigenem Code: (ist jetzt C, aber so 1:1 auf C++ übertragbar) -- ein Header aus einem Snake-Spiel, das "Futter" braucht eine Referenz auf den Screen (um sich zu zeichnen) und das Board (interner Spiel-Zustand), aber anstatt die jeweiligen Header zu includen gibt's hier nur eine forward declaration der structs (in C++ wären das dann eben class). Das vermeidet unnötige Abhängigkeiten.

  • Danke. Wegen dem Singleton. Ich glaube ich versuch wirklich stattdessen eine statische Klasse zu machen.

    Kann man machen. Du wirst aber immer jemand finden, der das, was du programmiert hast, anders gemacht hätte und auch jede Menge Gründe dafür hat. :P
    Speziell "liebe" ich Kritik an Pattern, die im konkreten Fall keine Problem machen, aber in irgendeinem anderen Kontext prinzipiell problematisch sein könnten (Singletons sind so ein Beispiel).


    Ich frage dann immer: Gibt's hier ein konkretes Problem? Nein? Dann bleibt das so. :D


    Ist halt immer die Frage, was im Vordergrund steht. Schöner Code oder eine funktierende Anwendung.

  • Ja, ich verstehe was Du meinst :) Kritiken gibt es immer. Nur in diesem Fall könnte ich gar nicht richtig begründen, warum ein Singleton und wie der funktioniert. Weil ich ihn mir nur abgeschaut habe. Ich kenne nur dessen Vorteile.


    Und wenn eine statische Klasse das selbe bietet, warum sollte ich das nicht machen? In diesem Fall könnte ich auch erklären, warum und wie sie funktioniert. Das Ändern selbst kostet ja nichts. Im besten Fall bin ich hinterher sogar klüger. Und im schlimmsten Fall? Naja, ist ja nicht so, das die Leute wegen meinem Code Schlange stehen ;)


    Und wenn ich ein Pattern benutze, was ich eigentlich gar nicht richtig verstanden habe, ist dies dann funktionierender Code? Oder ist funktionierender Code, wenn ich jede Zeile einem Außenstehenden erklären kann?

  • Und wenn eine statische Klasse das selbe bietet, warum sollte ich das nicht machen?

    Völlig ok. Wenn es eine statische Klasse tut, spricht ja auch alles dafür. Wenn eine bessere Lösung weniger oder gleichviel Aufwand erzeugt, bin ich auch immer dafür.


    Genau, ich meinte das eher grundsätzlich. Man muss halt irgendwann mal zu seinem Code stehen. Wenn man es allen recht machen will, wird man nie fertig. :D

  • Also ein Singleton ist kein Weltuntergang, so sollte das auch nicht rüberkommen. Es ist einfach nur so, dass die vermeintlichen Vorteile eigentlich keine sind. Wenn man keinen Zustand hat, ist das Singleton ohnehin sinnlos, und wenn man welchen hat, hat man die gleichen Probleme wie mit einer statischen Klasse.


    Ich hatte den Thread so verstanden, dass es hier auch darum geht, zu lernen und Erfahrung zu sammeln, anhand eines kleinen Projekts, das irgendwas "cooles" auf dem Bildschirm tut. Das ist IMHO auch die beste Vorgehensweise -- ich habe als ich endlich mal den Umgang mit curses (Eine Konsolen/Terminal-Abstraktion die vor allem auf Unix-Systemen verbreitet ist) lernen wollte einfach ein kleines Snake-Spiel geschrieben ;) Wenn du dich für Cross-Plattform Entwicklung interessierst könnte das übrigens noch ein interessanter Step sein: Eine alternative Implementierung deiner Console Klasse auf curses ;) Aber das nur am Rande.


    Wollte man das ganze flexibel und einfach unit-testbar implementieren, dann wäre Console eine ganz normale Klasse, zu der es ein Interface (in C++: abstrakte Basisklasse) gibt. Alle Klassen, die eine Konsole brauchen, hätten einen Konstruktor-Parameter vom Typ des Interfaces. Dann würdest du beim Programmstart hingehen und allen Objekten beim Erzeugen eine Referenz auf dein eines Console-Objekt übergeben. Dieses Pattern nennt sich IoC (Inversion of Control), weil nicht mehr der Verwender die konkrete Klasse kennen muss, die er verwendet, sondern von außen bekommt, was er benötigt. Damit kann man dann z.B. sehr einfach alternative Implementierungen "reinstecken", z.B. eben eine für eine andere Plattform, oder auch einen Fake oder Mock um gut Unit-testen zu können. Es gibt dafür auch Frameworks, die die ganze "Konstruiererei" deutlich erleichtern. In diesem Szenario ist ein Singleton nutzlos, du stellst einfach selbst sicher, dass du nur eine Instanz erzeugst :)


    Das ist jetzt selbstverständlich keine Empfehlung -- für dieses Projekt wäre IoC (mit "dependency injection") ganz sicher mit Kanonen auf Spatzen geschossen. Aber wenn du das nicht machst, haben alle Verwender von Console eine harte Abhängigkeit. Daran ändert eben auch das Singleton nichts, es tut nur so als würde es globalen Zustand "schön kapseln". Wie eingangs erwähnt, es ist keine Katastrophe, Singletons zu implementieren -- nach meiner Erfahrung braucht man sie nur einfach nicht ;)

  • Dann würdest du beim Programmstart hingehen und allen Objekten beim Erzeugen eine Referenz auf dein eines Console-Objekt übergeben.

    Dependency Injection über den Konstruktor ist eine Möglichkeit. Oder man verwendet einen IoC-Container.
    Das mache ich in C#, wenn ich mit Frameworks wie Prism oder auch MVVM-Light arbeite. Üblicherweise mt StructureMap oder Unity.


    Aber wie schon gesagt, das ist dann eine andere Komplexitätsstufe. ;)


    In meinen privaten Priojekten gibt es keine Unit-Tests und viele Singletons. :D

  • Dependency Injection über den Konstruktor ist eine Möglichkeit. Oder man verwendet einen IoC-Container.

    Das meinte ich oben mit Frameworks (die in den meisten Fällen letztendlich auch nichts anderes tun als Konstruktor-Injection, nur muss man das eben nicht mehr von Hand coden sondern kann einfach seine konkreten Typen "registrieren") -- ich wollte hier bewusst nicht zu weit ins Detail gehen, weil es für das Projekt hier völlig oversized wäre -- höchstens vielleicht als "Fingerübung" interessant, wenn man sich damit beschäftigen will.

    In meinen privaten Priojekten gibt es keine Unit-Tests und viele Singletons. :D

    Unit-Tests schreibe ich privat auch äußerst selten. Trotzdem verstehe ich nicht, welchen Vorteil das Singleton bieten soll? Es ist doch nichts als eine nutzlose Fassade, die den static/global Kram kaschiert -- so ungefähr wie in Russland zum Teil leuchtend blaue Glasfassaden vor die alten bröckelnden Platten gesetzt werden :D Aber egal, ich wundere mich hier nur, warum man den Aufwand treibt, Nutzen sehe ich eben keinen -- "Schaden" (im Vergleich zu simplen statischen Klassen) auch nicht wirklich.

  • Es gibt ja gar keine statische Klassen in C++. Nur statische Methoden und namespaces. Dann gibt es aber auch keine const-Methoden. Wobei ich jetzt gerade unsicher bin, weshalb das ein Vorteil ist.
    Hatte hier jetzt noch etwas Text, war aber mit viel Unsicherheiten behaftet. Deshalb die simple Frage,


    Wie können statische Methoden oder ein namespace den Vorteil eines Singleton ausgleichen?

  • Zuerst mal zu "es gibt keine statischen Klassen in C++": Es gibt auch keine Singletons in C++ (oder den meisten anderen Sprachen), es gibt keine Klassen in (altem) JavaScript oder gar in C, es gibt in C++ und in C keine Interfaces, usw, jedenfalls als Sprachkonstrukt -- "bauen" kann man das alles trotzdem :) Ob ein bestimmtes Konstrukt oder auch Pattern von einer Sprache fertig angeboten wird oder es nur Möglichkeiten gibt, es "von Hand" in eigenem Code zu bauen, ist oft nachrangig. Eine "statische Klasse" in C++ ist einfach eine Klasse ohne Felder und mit ausschließlich statischen Methoden. Wenn man die völlig sinnlose Instanziierung verhindern will packt man noch einen privaten Konstruktor dazu, der wird beim compilieren dann sowieso wegoptimiert.


    Jetzt zu "Vorteil ausgleichen": Welchen Vorteil denn genau? Konzeptionell gesehen ist beides das gleiche.


    Ein Vorteil von Klassen ist die sehr gute Testbarkeit mit Unit-Tests wenn man konsequent nur Interfaces referenziert und das IoC Prinzip durchzieht. Das führt das Singleton aber sowieso ad absurdum, man kann hier immer nur eine Instanz der immer gleichen Klasse bekommen.


    Bleiben also zwei Fälle:


    1. Es gibt keinen Zustand. Das ist der "gute" Fall, eine statische Klasse bietet genau das. Das Singleton schadet auch nicht wirklich, ist aber einfach überflüssig ... man hat eine Instanz eines leeren Objekts für nichts (und genaugenommen würden mehrere Instanzen auch nicht schaden, sie sind ja sowieso leer)


    2. Es gibt Zustand. Das ist der "blöde" Fall, den das Singleton "elegant" lösen will. Tut es aber nicht wirklich. Egal ob Singleton oder einfach statische Klasse, man hat globalen Zustand, der (potentiell auch thread-sicher) gemanaget werden muss. Das heißt dann auch Vorsicht bei jedem Aufruf, der diesen globalen Zustand verwendet --- soll es thread-sicher sein, kommt man um einige locks nicht herum. Besonders interessant ist hier die erste Initialisierung. Google mal nach "double-checked locking" insbesondere in C++, ein berühmt-berüchtigter "fail" in der Softwaretechnik :) Man hat hier das exakt gleiche Problem, egal ob mit Singleton und Konstruktor oder einfach mit z.b. einer lokalen Initialisierungsfunktion mit static linkage. Das Singleton gaukelt IMHO eine elegante Lösung nur vor. Am besten ist es, immer erst einmal einige Mühe und Gedanken zu investieren um eine Lösung zu suchen, die ohne globalen Zustand auskommt.