Kleinere Probleme beim Ausprobieren des Bridge-Patterns



  • Hi,

    ich spiele gerade ein bisschen mit Design Patterns herum und habe gerade ein kleines Problem bei der Umsetzung vom Bridge-Pattern. Ist zwar in C++, weiß aber nicht ob das Problem eher "Rund um Programmierung" gehört, bitte ggf. um Verschiebung.

    Ich versuche grade das Pattern in Verbindung mit Gtk einzusetzen (ob das Konzept an sich sinnvoll ist, sei mal dahingestellt). Dabei hab ich gerade in etwa folgende Struktur gebastelt:

    http://picload.org/image/lpdlcdw/bridge.jpg

    Das Problem was ich habe ist im Diagramm schon angedeutet. Die Implementierung der Application Klasse mittels Gtk braucht ja letztlich Zugriff auf die Gtk eigenen Window-Klassen, die in der abstrakten Application-Klasse natürlich nicht berücksichtigt werden. Wie genau muss ich nun vorgehen, damit ich bezüglich der momenta nicht möglichen Konvertierung zwischen Window und Gtk::Window keine Typfehler mehr bekomme? An welcher Stelle muss ich also für die Window-Klasse die spezielle Gtk Implementierung auswählen (mit Abstract-Factory?)? (Wahrscheinlich einfach, aber ich komm leider nicht drauf).

    Vielen Dank für eure Hilfe.



  • Mir ist dein Problem nicht so wirklich klar, und auch nicht, wo du da die Bridge siehst und was sie genau machen soll. Beschreib mal in Worten, was dein Diagramm aussagen soll und was du eigentlich machen willst.



  • Mein grundlegender Gedankengang war folgender:

    Ich wollte eine Applikation haben, die die UI mittels einer der vielen zur Verfügung stehenden Gui-Toolkits umsetzt. Dabei möchte ich aber in der Applikation selbst nicht gegen das Toolkit-Interface selbst sondern gegen ein generisches Interface programmieren. Außerdem sollte es möglich sein, das verwendete Toolkit relativ einfach austauschbar zu machen. Aus meiner Sicht trifft auf diese Problemstellung das Bridge Pattern relativ gut zu (andere Vorschläge gerne gesehn, bin in sowas absoluter Neuling).

    Ich habe dann mal ganz einfach mit den generischen Interfaces zu den Applikations- und Window-Klassen (die ja typischerweise in den Gui-Toolkits Anwendung finden) angefangen. So sieht das jetzt mal vereinfacht aus:

    namespace Gui
    {
    class Application
    {
    public:
        Application(std::string id)
        {
            impl = std::unique_ptr<ApplicationImpl>(new GtkApplicationImpl(id));
        };
    
        virtual ~Application() {};
    
        };
        virtual void run(Window& window)
        {
            impl->run(window);
        };
    
    private:
        std::unique_ptr<ApplicationImpl> impl;
    };
    }
    
    namespace Gui
    {
    
    class ApplicationImpl
    {
    public:
        ApplicationImpl() = delete;
        virtual ~ApplicationImpl() {};
    
        virtual void run(Window& window, int argc, char** argv) = 0;
        virtual void run(Window& window) = 0;
    
    protected:
        ApplicationImpl(std::string id) {};
        ApplicationImpl(std::string id, int argc, char** argv) {};
    };
    };
    
    namespace Gui
    {
    class GtkApplicationImpl : public ApplicationImpl
    {
    public:
        GtkApplicationImpl(std::string id);
        GtkApplicationImpl(std::string id, int argc, char** argv);
        virtual ~GtkApplicationImpl();
    
         void run(Window& window) override {app->run(window);}; // hier gibts entsprechend einen Typfehler
    
    private:
        Glib::RefPtr<Gtk::Application> app;
    };
    } // End namespace Gui
    

    Die run-Funktion bspw. erwartet in den Interfaces eine Referenz der Klasse Gui::Window (also quasi meine Abstraktion). Spätestens in der GtkApplicationImpl-Klasse muss aber die dort vorhandene run-Funktion von Gtk auch ein entsprechendes Gtk::Window als Referenz erhalten. Nun krieg ich natürlich einen Typfehler, da ein Gui::Window von mir erstmal kein Gtk::Window ist. Meine Klasse Gui::Window hat denselben Aufbau wie die oben dargestellte Gui::Application-Klasse, hat also einen Zeiger auf eine entsprechende Implementierung, etc.

    Meine Frage ist nun, wie bekomm ich es hin, dass aus dem Gui::Window in der Implementierung ein Gtk::Window wird (kann es grad nicht besser beschreiben)?. Ich hoffe das mein Anliegen etwas klarer geworden ist und es nicht zu konfus geworden ist.



  • Ich schätze, mit Polymorphie kommst du hier nicht weiter, da dazu ja die konkreten Toolkit-Klassen wie Gtk::Window Subklassen deiner Window -Klasse sein müssten. Du könntest mit dem typeid -Operator arbeiten, passender wäre hier aber wohl die Benutzung von Templates.

    edit: Vergiss das mit typeid , du kannst auch Polymorphie benutzen, aber eben nur innerhalb deiner Klassenhierarchie, die Gtk::Window s etc. musst du dann in deinen jeweiligen Implementationen selber korrekt benutzen. Aber das selbe geht wie gesagt besser mit Templates, vermeidet Laufzeitoverhead und in diesem Fall auch code-bloat.



  • Meiner schrieb:

    Dabei möchte ich aber in der Applikation selbst nicht gegen das Toolkit-Interface selbst sondern gegen ein generisches Interface programmieren.

    Das ist eigentlich ein grundlegend falscher Ansatz oder du hast es zumindest so formuliert, dass es den Eindruck macht, und es ist einfach nur ein schlechtes Beispiel. Du solltest gar nicht gegen irgendwelche Toolkit Interfaces programmieren, dein Anwendungskern darf überhaupt nichts über die GUI wissen. Es muss immer anders rum gehen, die GUI kennt deine Anwendungslogik und greift darauf zu. Dann kannst du auch problemlos eine andere GUI schreiben, ändert ja nichts an dem Kern deines Programms. Auch eine Klasse wie "Application" ist Teil der GUI, sowas brauchst du in deinem Kern nicht, und schon gar keine Fenster.
    Um hier eine Bridge zu machen, müsstest du wahrscheinlich etwas weiter ausholen und für jede Implementierung Adapter schreiben.

    class QtWindowAdapter: public QWidget
    {
    public:
      QtWindowAdapter(Window * window);
    };
    
    class QtApplicationImpl: public ApplicationImpl
    {
    public:
      void run(Window * window)
      {
        QtWindowAdapter * adapter = new QtWindowAdapter(window);
        //use it
      }
    };
    

    Kann aber natürlich kompliziert werden bei umfangreicheren Klassenhierarchien. Ich sags dir auch nochmal, würd ich mit der GUI ganz sicher nicht machen. Wenn dein Programm die GUI nicht kennt, sondern die GUI dein Programm, hast du das Problem überhaupt nicht.



  • So wie du das aufgezogen hast, hast du hier zwei Bridges.
    Also zwei Abstraction-Hierarchien und zwei Implementation-Hierarchien. Wenn die beiden Implementierungen zusammen arbeiten müssen, dann gibt's oft Probleme. Vor allem wenn die Zusammenarbeit über die Abstractions "gesteuert" werden soll/muss.

    Eine Möglichkeit wäre der ApplicationImpl Klasse einfach Zugriff auf die WindowImpl s zu geben.
    Also z.B. Gui::Window erklärt ApplicationImpl zum friend , und die GtkApplicationImpl holt sich dann einfach den WindowImpl Zeiger. Den kann sie dann zu GtkWindowImpl downcasten.

    Eine andere Möglichkeit wäre den run() Aufruf einfach ans Fenster weiterzuleiten, ala

    class Application ...
    {
    public:
        void run(Window& wnd) { wnd.runWithApplication(m_impl); }
    // ...
    };
    
    class Window ...
    {
    public:
        void runWithApplication(ApplicationImpl& app) { m_impl->runWithApplication(app); }
    // ...
    };
    
    class WindowImpl ...
    {
    public:
        virtual void runWithApplication(ApplicationImpl& app) = 0;
    // ...
    };
    
    class GtkWindowImpl ...
    {
    public:
        virtual void runWithApplication(ApplicationImpl& app)
        {
            GtkApplicationImpl& gtkApp = dynamic_cast<GtkApplicationImpl&>(app);
            gtkApp.run(*this); // TADAAA
        }
    };
    


  • Vielen Dank für eure Rückmeldungen.

    @hustbaer:

    Ich glaube ich bin da gerade in etwa auf den selben Gedanken gekommen (ich glaube in leicht abgewandelter Form):

    Die GtkWindowImpl-Klasse sieht wie folgt aus:

    class GtkWindowImpl : public WindowImpl, private Gtk::Window
    {
    public:
        GtkWindowImpl();
        ~GtkWindowImpl();
    
        virtual WindowImpl& getInstance() override {return *this;};
    };
    

    Dann kann in der Application (also in der entsprechenden Gtk-Implementierung in diesem Fall) ebenfalls mit casts gearbeitet werden:

    void run(Window& window) override {app->run(dynamic_cast<Gtk::Window&>(window.impl->getInstance()));};
    

    @Mechanics

    Ich bin mir sogar fast sicher, dass ich hier einen falschen Gedankengang drin habe. Mir erscheint das Ganze nach den ersten Schritten ebenfalls viel zu aufwändig. Denn es ist ja wie du sagst, ich müsste jetzt Bridges und Adapter für quasi alles was ich aus den Gui-Bibliotheken benutzen will schreiben.

    Da ich noch nie wirklich Programme mit Guis geschrieben habe, weiß ich aber nicht so recht wie ich nun weiter vorzugehen habe, bzw. wie ein Klassendesign diesbezüglich aussehen könnte.
    Nehmen wir mal an, ich habe nun ein Programm mit der fertigen Programmlogik. Wie genau würde man nun das User-Interface kapseln (oder abstrahieren, etc.) und wie gestaltet man es so, dass ein Austausch der verwendeten Toolkits etc. noch möglich wird? Kann man dazu evtl. ein ganz einfaches Beispiel oder so finden was mir da weiterhelfen könnte? Es hapert bei mir da anscheinend am allgemeinen Verständnis am Zusammenhang Programm <-> Interface. Im Netz stoße ich diesbezüglich meistens nur auf Beispiele, in denen mehr auf die konkrete Verwendung von Bibliotheken eingegangen wird, nicht auf das grundsätzlich Design einer Anwendung, die solche Bibliotheken nur "unterstützend" benutzen soll.



  • Meiner schrieb:

    Dann kann in der Application (also in der entsprechenden Gtk-Implementierung in diesem Fall) ebenfalls mit casts gearbeitet werden:

    void run(Window& window) override {app->run(dynamic_cast<Gtk::Window&>(window.impl->getInstance()));};
    

    Genau das geht eben nicht, da dein GtkWindowImpl ja private von Gtk::Window erbt. Außerdem brauchst du zum Upcasten keinen expliziten Cast, das geht auch implizit.

    Meiner schrieb:

    Da ich noch nie wirklich Programme mit Guis geschrieben habe, weiß ich aber nicht so recht wie ich nun weiter vorzugehen habe, bzw. wie ein Klassendesign diesbezüglich aussehen könnte.
    Nehmen wir mal an, ich habe nun ein Programm mit der fertigen Programmlogik. Wie genau würde man nun das User-Interface kapseln (oder abstrahieren, etc.) und wie gestaltet man es so, dass ein Austausch der verwendeten Toolkits etc. noch möglich wird?

    Ohne jetzt anmaßend sein zu wollen, aber wie wärs denn, wenn du erst mal ein schönes GUI-Programm mit einem netten Toolkit programmierst? Und dann vllt. nochmal eins (kann auch das selbe sein) unter Verwendung eines anderen Toolkits? Dann lernst du diese ganze Welt erst mal kennen. Ist ja gut wenn du ambitioniert bist und du kannst es auch mal so probieren (Stroustrup z.B. empfiehlt ja eh, den ersten Entwurf von gräßeren Softwaresystemen grundsätzlich wegzuschmeißen und mit der gemachten Erfahrung nochmal neu anzufangen), gerade auch wenn es nur zum Spielen ist oder nur ein sehr kleiner Teil abgedeckt werden soll, aber ansonsten ist ein Interface schreiben zu wollen, das die Verwendung unterschiedlicher Toolkits erlaubt ohne sich bei deren Funktionsweise auszukennen meines Erachtens die falsche Vorgehensweise.
    Gutes Design ist halt auch gerade eine der größten Künste beim Programmieren und wie man eindrücklich an der WinAPI und der Android API sieht kriegen das auch Microsoft und Google nicht richtig hin (ich zumindest finde die APIs furchtbar). Es ist so schon herausfordernd, da sollte man wenigstens genau wissen, wie das zu designende System genau arbeitet.



  • philipp2100 schrieb:

    Genau das geht eben nicht, da dein GtkWindowImpl ja private von Gtk::Window erbt. Außerdem brauchst du zum Upcasten keinen expliziten Cast, das geht auch implizit.

    Mhh ok, ich hatte das gestern Abend nur kurz durch den Compiler gejagt und da gabs zumindest keine Typfehler mehr. Auf die Gefahr hin mich weiter als totaler Anfänger zu outen, könntest du kurz erklären, warum mein Ansatz nicht funktioniert? (Ich lese bei dir heraus, dass das an der private Vererbung liegt?)

    philipp2100 schrieb:

    Ohne jetzt anmaßend sein zu wollen, aber wie wärs denn, wenn du erst mal ein schönes GUI-Programm mit einem netten Toolkit programmierst? ... Es ist so schon herausfordernd, da sollte man wenigstens genau wissen, wie das zu designende System genau arbeitet.

    Ich bin ja für jede Hilfe dankbar und denke auch das du Recht hast. Bin da momentan wohl etwas zu überambitioniert. Dennoch bleibt für mich weiterhin (also ohne den ganzen Schnickschnack bzgl. austauschbare Toolkits, etc.) die Frage, wie man nun genau an das Erstellen einer Gui für ein Programm rangeht (im Sinne von Klassenaufbau, welche braucht man, was haben die typischerweise für Methoden, etc.). Am liebsten wäre mir quasi mal den Beispielcode von einem gut strukturierten (aber kleinem) Programm zu sehn, um ein realitätsnahes Bsp. diesbezüglich zu haben. Ansonsten wird ich wohl erstmal deinem Vorschlag entsprechen und besser klein anfangen.



  • Wenn du über dein Klassendesign nachdenken musst machst du es falsch. "Ich habe eine Anwendungslogik, ich habe ein Ding was Fenster macht, jetzt schreibe ich eine Klasse, die eine Anwendungslogik und ein Fenster-Ding hat und stelle die Anwendungslogik im Fenster dar."
    Keine Vererbung, keine Interfaces, keine Casts. Einfach nur simpel hinschreiben was passieren soll. Damit kannst du das Fenster-Toolkit wechseln ohne die Anwendungslogik anzufassen und umgekehrt. Generischer und einfacher wird es nicht. Ich halte es langsam für einen Fehler Leuten überhaupt Vererbung und seltsame Design-Patterns beizubringen.



  • Meiner schrieb:

    könntest du kurz erklären, warum mein Ansatz nicht funktioniert? (Ich lese bei dir heraus, dass das an der private Vererbung liegt?)

    Ja genau, denn wenn du private erbst, hat die erbende Klassen die ganzen geerbten Methoden nach außen hin ja nicht. Dementsprechend wäre es sinnfrei, ein wie auch immer geartetes Casten zuzulassen.

    Meiner schrieb:

    Am liebsten wäre mir quasi mal den Beispielcode von einem gut strukturierten (aber kleinem) Programm zu sehn, um ein realitätsnahes Bsp. diesbezüglich zu haben.

    Ich kanns mal versuchen:

    enum Toolkit { TK_GTK, TK_WXWIDGETS, TK_QT };
    
    template <int toolkit>
    class Window {
      public:
        virtual void show() = 0;
    };
    
    template <>
    class Window<TK_GTK> : protected Gtk::Window {
      public:
        virtual void show() { Gtk::Window::show(); }
    };
    
    template <>
    class Window<TK_WXWIDGETS> {
        wxFrame* frame;
      public:
        virtual void show() { frame->show(); }
    };
    

    In dem Beispiel gehe ich davon aus, dass sich ein wxFrame besser als Member handlen lässt. Für das gtk::Window nehme ich protected -Vererbung, damit man auch in abgeleiteten Klassen weiter Zugriff darauf hat - das oben angesprochene Problem tritt aber wie auch bei der private -Vererbung auf. So würdest du halt für jedes Toolkit so Spezialisierungen von deinen generischen Klassentemplates machen.



  • nwp3 schrieb:

    Wenn du über dein Klassendesign nachdenken musst machst du es falsch.

    Sorry, aber ich hab praktisch noch nie einen dämlicheren Spruch über Softwaredesign gehört. Design ist mit das Kritischste überhaupt für ein qualitativ hochwertiges größeres Projekt. C++ ist ja extrem reich an Ausdrucksmöglichkeiten und dadurch eine sehr komplexe Sprache. Zu behaupten, einem Anfänger oder Fortgeschrittenem in der Sprache (oder auch einem Profi) würde das Design eines kompletten Toolkits einfach so zufliegen, ist wirklichkeitsfremd.

    nwp3 schrieb:

    "Ich habe eine Anwendungslogik, ich habe ein Ding was Fenster macht, jetzt schreibe ich eine Klasse, die eine Anwendungslogik und ein Fenster-Ding hat und stelle die Anwendungslogik im Fenster dar." [...] Generischer und einfacher wird es nicht.

    Ganz so einfach ist es eben nicht, ein Fenster hat halt auch noch Elemente etc., die u.U. in jedem Toolkit völlig anders programmiert werden müssen. Ich denke, du hast das Problem nicht verstanden.



  • philipp2100 schrieb:

    nwp3 schrieb:

    Wenn du über dein Klassendesign nachdenken musst machst du es falsch.

    Sorry, aber ich hab praktisch noch nie einen dämlicheren Spruch über Softwaredesign gehört. Design ist mit das Kritischste überhaupt für ein qualitativ hochwertiges größeres Projekt. C++ ist ja extrem reich an Ausdrucksmöglichkeiten und dadurch eine sehr komplexe Sprache. Zu behaupten, einem Anfänger oder Fortgeschrittenem in der Sprache (oder auch einem Profi) würde das Design eines kompletten Toolkits einfach so zufliegen, ist wirklichkeitsfremd.

    Ich will nicht sagen, dass es einfach ist sich ein kompliziertes Klassendesign zu überlegen. Ich will sagen, dass du statt dir ein kompliziertes Klassendesign zu überlegen lieber die Realität ansehen solltest. Public Vererbung bedeutet "ist ein". Private Vererbung heißt "Ist implementiert mithilfe von". Eine Membervariable bedeutet "hat ein". Und jetzt vergiss alles was du über Klassendesign weißt und schreibe auf was dein Programm so tut. Automatisch ergibt sich das Klassendesign indem du "Hat ein", "Ist ein" und "Ist implementiert mit" 1 zu 1 in C++ übersetzt.
    Ich befürchte, dass du mit deiner Klassendesignerei völlig an der Realität vorbei ziehst und viele unnötige Probleme erschaffst, die daher rühren, dass dein kompliziertes Klassenmodell und das Problem auseinander driften. Mir ist auch unklar wie diese Realität aussieht, die man mithilfe von deinen benutzten Ausdrucksmitteln beschreibt. Du solltest diese komplizierten Ausdrucksmöglichkeiten nur nutzen wenn du musst, nicht wenn du kannst.

    philipp2100 schrieb:

    nwp3 schrieb:

    "Ich habe eine Anwendungslogik, ich habe ein Ding was Fenster macht, jetzt schreibe ich eine Klasse, die eine Anwendungslogik und ein Fenster-Ding hat und stelle die Anwendungslogik im Fenster dar." [...] Generischer und einfacher wird es nicht.

    Ganz so einfach ist es eben nicht, ein Fenster hat halt auch noch Elemente etc., die u.U. in jedem Toolkit völlig anders programmiert werden müssen. Ich denke, du hast das Problem nicht verstanden.

    Ich sehe den Widerspruch nicht. Ein Toolkit ist ein Ding was den Zustand der Logik darstellen kann. Das kann man mit einer einfachen Funktion ausdrücken: void Toolkit::display(const Anwendungslogik &al); Der Rest des Toolkits ist private und kümmert sich darum wie man GTK/WinAPI/QT dazu bringt die Anwendungslogik darzustellen. Ich verstehe nicht warum du dafür mit virtuellen Funktionen, Pimpl und sonstigem Gedöns rummachst.



  • nwp3 schrieb:

    Ich will nicht sagen, dass es einfach ist sich ein kompliziertes Klassendesign zu überlegen. Ich will sagen, dass du statt dir ein kompliziertes Klassendesign zu überlegen lieber die Realität ansehen solltest. Public Vererbung bedeutet "ist ein". Private Vererbung heißt "Ist implementiert mithilfe von". Eine Membervariable bedeutet "hat ein". Und jetzt vergiss alles was du über Klassendesign weißt und schreibe auf was dein Programm so tut. Automatisch ergibt sich das Klassendesign indem du "Hat ein", "Ist ein" und "Ist implementiert mit" 1 zu 1 in C++ übersetzt.

    Ganz so einfach ist es eben nicht mehr, wenn der Umfang mal größer wird. Da gibt es dann eben Konzepte wie "verwaltet Speicher für", "bindet ein", "ist Handle für", "ist Interface-Klasse für" etc., die entsprechend komplexer modelliert werden müssen.

    nwp3 schrieb:

    Das kann man mit einer einfachen Funktion ausdrücken: void Toolkit::display(const Anwendungslogik &al); Der Rest des Toolkits ist private und kümmert sich darum wie man GTK/WinAPI/QT dazu bringt die Anwendungslogik darzustellen. Ich verstehe nicht warum du dafür mit virtuellen Funktionen, Pimpl und sonstigem Gedöns rummachst.

    Und wie willst du die display() -Funktion schreiben, ohne den Rest des Programms zu kennen?

    edit: Damit du mich nicht falsch verstehst: Im Grunde bin ich ganz bei dir und sehe auch ein großes Problem in der Verwendung unnötig komplizierter Designlogiken. Da muss man schon drauf achten, aber ich würde das jetzt nicht als unnötig kompliziert erachten. Wenn du eine bessere Idee hast dann poste!



  • philipp2100 schrieb:

    nwp3 schrieb:

    Das kann man mit einer einfachen Funktion ausdrücken: void Toolkit::display(const Anwendungslogik &al); Der Rest des Toolkits ist private und kümmert sich darum wie man GTK/WinAPI/QT dazu bringt die Anwendungslogik darzustellen. Ich verstehe nicht warum du dafür mit virtuellen Funktionen, Pimpl und sonstigem Gedöns rummachst.

    Und wie willst du die display() -Funktion schreiben, ohne den Rest des Programms zu kennen?

    Die display-Funktion hat doch alles. Sie hat über den Parameter Zugriff auf den öffentlichen Zustand der Programmlogik und über die Toolkit::-Zugehörigkeit Zugriff auf sämtliche Fensterfunktionen. Was fehlt denn?

    philipp2100 schrieb:

    Ganz so einfach ist es eben nicht mehr, wenn der Umfang mal größer wird. Da gibt es dann eben Konzepte wie "verwaltet Speicher für", "bindet ein", "ist Handle für", "ist Interface-Klasse für" etc., die entsprechend komplexer modelliert werden müssen.

    Und hier liegt dein Denkfehler. "verwaltet Speicher für" gibt es in so einer Anwendung gar nicht. Die allermeisten Anwendungen interessieren sich nicht für Speicher. Das ist ein Implementationsdetail und muss als solches behandelt werden. "bindet ein" verstehe ich nicht bzw. kann mit verschiedene Dinge drunter vorstellen. "ist Interface-Klasse für" existiert außerhalb von Java-Interpretern u.ä. nicht und ist damit nicht Teil des Klassendesigns. Dein Klassendesign darf nur Dinge enthalten, die von der Anwendung gefordert sind und schon die Existenz einer Klasse wird praktisch nie gefordert. Nachdem du modelliert hast was die Anwendung tun soll kannst du dich in der Implementation mit dem Bridge-Pattern beschäftigen wenn du gerne möchtest. Aber dann ist alles so weit abstrahiert und gekapselt, dass es nur relativ kleine Module sind, die sowas hoffentlich nicht erfordern.



  • nwp3 schrieb:

    Die display-Funktion hat doch alles. Sie hat über den Parameter Zugriff auf den öffentlichen Zustand der Programmlogik und über die Toolkit::-Zugehörigkeit Zugriff auf sämtliche Fensterfunktionen. Was fehlt denn?

    Eine generische display()-Funktion, die Teil eines Toolkits ist, das VOR dem eigentlichen Programm entworfen wird, kann doch gar nichts von der Programmlogik wissen, die sie darstellen soll.

    nwp3 schrieb:

    "verwaltet Speicher für" gibt es in so einer Anwendung gar nicht.

    Doch, sogar in jeder größeren gibt es eine Speicherverwaltung, die selbstverständlich auch objektorientiert designt werden kann.

    nwp3 schrieb:

    Die allermeisten Anwendungen interessieren sich nicht für Speicher. Das ist ein Implementationsdetail und muss als solches behandelt werden.

    Was dich aber nicht davon entbindet, es irgendwie und irgendwo zu implementieren.

    nwp3 schrieb:

    "bindet ein" verstehe ich nicht bzw. kann mit verschiedene Dinge drunter vorstellen.

    Könnte z.B. eine Klasse einer Pluginschnittstelle sein, die Plugins verwaltet.

    nwp3 schrieb:

    "ist Interface-Klasse für" existiert außerhalb von Java-Interpretern u.ä. nicht und ist damit nicht Teil des Klassendesigns.

    Guck mal in den Stroustrup. Dort wird der Begriff (auch ziemlich naheliegend) wie ich finde für Klassen verwendet, die ein anderes (einfacheres oder vielseitigeres) Interface für andere Klassen bereit stellt verwendet.



  • philipp2100 schrieb:

    nwp3 schrieb:

    Die display-Funktion hat doch alles. Sie hat über den Parameter Zugriff auf den öffentlichen Zustand der Programmlogik und über die Toolkit::-Zugehörigkeit Zugriff auf sämtliche Fensterfunktionen. Was fehlt denn?

    Eine generische display()-Funktion, die Teil eines Toolkits ist, das VOR dem eigentlichen Programm entworfen wird, kann doch gar nichts von der Programmlogik wissen, die sie darstellen soll.

    Klar kann sie. Wir programmieren jetzt mal StarCraft nach:

    struct Unit{
        enum class Type{Zergling, Zealot, Siegetank, ...};
        Type t;
        //kram hier unten interessiert für display eigentlich nicht
        int currentHP;
        int maxHP; //Funktion wegen upgrades
        int damage;
        int defence;
    };
    
    struct Logic{
        vector<Unit> units; //Rumrennende Einheiten
        vector<Object> objects; //Fliegende Schüsse und sonstiges
    private:
        //Unwichtiger Kram
    };
    
    void display(const Logic &l){
        for (auto &u : l.units){
            renderUnit(u);
        }
        renderMap(); //map nach den Einheiten rendern weil pixel nicht überschrieben werden
    }
    

    Da fehlen noch ein paar Details. Der Punkt ist, dass man das schreiben kann bevor man diese kennt.
    Vielleicht ist das auch eine Glaubensfrage ob man Programme Top-Down oder Bottom-Up schreibt. Ich bin für Top-Down, du scheinbar nicht.

    philipp2100 schrieb:

    nwp3 schrieb:

    "verwaltet Speicher für" gibt es in so einer Anwendung gar nicht.

    Doch, sogar in jeder größeren gibt es eine Speicherverwaltung, die selbstverständlich auch objektorientiert designt werden kann.

    Ich glaube wir haben unterschiedliche Vorstellungen was eine Anwendung ist. Mein Verständnis von Anwendung ist sowas wie StarCraft. StarCraft hat die Anforderung, dass Zerglinge rummrennen und Gebäude fressen können. StarCraft hat niemals die Anforderung, dass ein Zergling ein Objekt einer Klasse ist. StarCraft hat auch nicht die Anforderung, dass die Einheiten in einem vector stecken oder in einer abstrakten Liste und stellt allgemein keine Anforderungen an Speicher. Nur Compiler, Betriebssystem und VMs tun sowas. Wenn man selbst eine Speicherverwaltungsklasse schreiben will, dann gibt es dafür natürlich Anforderungen. Die sind aber von der Natur "haben weniger als 10% Platz-Overhead und wenig Cache-Misses und implementieren diese Funktionen in O(x)" und nicht "leitet privat von Klasse X ab". Damit sind die dann auch von sich aus austauschbar ohne verbungs-cast-Gefrickel.



  • nwp3 schrieb:

    philipp2100 schrieb:

    Eine generische display()-Funktion, die Teil eines Toolkits ist, das VOR dem eigentlichen Programm entworfen wird, kann doch gar nichts von der Programmlogik wissen, die sie darstellen soll.

    Klar kann sie. Wir programmieren jetzt mal StarCraft nach:

    struct Unit{
        enum class Type{Zergling, Zealot, Siegetank, ...};
        Type t;
        //kram hier unten interessiert für display eigentlich nicht
        int currentHP;
        int maxHP; //Funktion wegen upgrades
        int damage;
        int defence;
    };
    
    struct Logic{
        vector<Unit> units; //Rumrennende Einheiten
        vector<Object> objects; //Fliegende Schüsse und sonstiges
    private:
        //Unwichtiger Kram
    };
    
    void display(const Logic &l){
        for (auto &u : l.units){
            renderUnit(u);
        }
        renderMap(); //map nach den Einheiten rendern weil pixel nicht überschrieben werden
    }
    

    Damit hast du jetzt ein Interface geschaffen, das jedes Programm auf Biegen und Brechen benutzen muss und nicht eben das schönste, würde ich sagen. Stell dir mal vor, GTK hätte so ein Interface. Klar das würde fürs Anzeigen zunächst gehen, aber was ist mit Interaktionen wieder zurück, z.B. reagieren auf Mausklicks?

    nwp3 schrieb:

    Ich glaube wir haben unterschiedliche Vorstellungen was eine Anwendung ist.

    Eher davon was Design ist.

    nwp3 schrieb:

    StarCraft hat auch nicht die Anforderung, dass die Einheiten in einem vector stecken oder in einer abstrakten Liste und stellt allgemein keine Anforderungen an Speicher. Nur Compiler, Betriebssystem und VMs tun sowas. Wenn man selbst eine Speicherverwaltungsklasse schreiben will, dann gibt es dafür natürlich Anforderungen.

    Wie gesagt, es geht hier aber um Designüberlegungen und nicht um Anforderungen.
    Und Design beinhaltet natürlich auch ganz klar das Zusammenspiel der Klassen bzw. welche Klassen es überhaupt gibt usw.

    edit/PS: Lass uns diese Diskussion vllt. lieber in einen anderen Thread oder per PN fortsetzen, wir sind ja schon ziemlich offtopic hier..



  • Meiner schrieb:

    Da ich noch nie wirklich Programme mit Guis geschrieben habe, weiß ich aber nicht so recht wie ich nun weiter vorzugehen habe, bzw. wie ein Klassendesign diesbezüglich aussehen könnte.
    Nehmen wir mal an, ich habe nun ein Programm mit der fertigen Programmlogik. Wie genau würde man nun das User-Interface kapseln (oder abstrahieren, etc.) und wie gestaltet man es so, dass ein Austausch der verwendeten Toolkits etc. noch möglich wird?

    Um vielleicht auf das Thema zurückzukommen... Ich denke, es ist kaum möglich, bei der GUI Programmierung besonders viel zu abstrahieren. GUI Programmierung ist oft nämlich komplexer, als viele annehmen. Es geht ganz selten darum, irgendein Fenster zusammenzuklicken, wo man 2-3 Werte einstellen kann, zumindest bei uns. Wir entwicklen mit Qt und unsere GUI ist ziemlich komplex und wird auch ständig komplexer und da muss man alle Möglichkeiten des Frameworks ausschöpfen (und das erweitern). Und da unterscheiden sich die Frameworks zu stark, um hier sinnvoll viel abstrahieren zu können. Wenn wir z.B. mal sowas wie einen Einstellungsdialog für eine Suchfunktion einbauen, kommen ziemlich sicher sofort zig Ideen und Anforderungen von allen möglichen Kunden und Consultants. Weil die Software einfach so komplex und umfangreich ist, dass es sehr viele Möglichkeiten und viele Informationen gibt und da will keiner irgendwelche Fenster ala Windows 3.1 sehen. Und dann schreibt man halt in Qt zig Models, ProxyModels, ItemDelegates, eigene Controls, Validatoren Ableitungen von Styles usw. Und das ist schon ziemlich frameworkspezifisch, in Gtk würde man das wohl komplett anders machen. Da will ich sicher keine zusätzliche Abstraktionsschicht dazwischenbauen, da dreht man ja durch. Dann gibts noch andere Aspekte, die das alles komplizierter machen, weil die GUI muss z.B. verzögerungsfrei reagieren, deswegen werden viele Sachen in Seitenthreads ausgeführt. Ist aber oft auch nicht einfach, weil für einige Sachen verwendet man am besten einen Threadpool, und andere muss man zwar im Hintergrund ausführen, aber serialisieren, weil das sonst kontraproduktiv wäre. Und einige der Hintergrundjobs muss man auch zusammenfassen, um nicht zu viel Traffic oder zu viele Anfragen zu erzeugen. Das ist dann wieder nicht so wirklich vom Framework abhängig und kann wiederverwendet werden, aber das sind Sachen, an die man vorher nicht unbedingt denkt und die alles komplizierter machen. Und dann kommen noch ganz lustige Sachen, wenn man auf irgendwelche sehr speziellen Probleme eingehen muss, weil ein Partner z.B. paar von unseren Fenstern als Plugin in einem exotisch CAD System verwendet und dabei sporadisch Probleme auftreten, wo man dann schlimmstenfalls Wochen debuggen muss, um den Fehler oder einen Workaround zu finden.



  • nwp3 schrieb:

    Da fehlen noch ein paar Details. Der Punkt ist, dass man das schreiben kann bevor man diese kennt.

    Die von dir gezeigte Display-Funktion ist mit ihren 3 1/2 Zeilen völlig trivial, und damit völlig uninteressant. Und je nicht-trivialer und interessanter sie wird, desto mehr schränkt sie ein.
    Oft so sehr, dass sie einem eine weitere Vorgehensweise aufzwingt, die einfach überhaupt nicht gut ist. Weswegen es z.B. auch GUI Toolkits gibt die für bestimmte Dinge einfach überhaupt nicht gut geeignet sind.

    nwp3 schrieb:

    Da fehlen noch ein paar Details. Der Punkt ist, dass man das schreiben kann bevor man diese kennt.
    Vielleicht ist das auch eine Glaubensfrage ob man Programme Top-Down oder Bottom-Up schreibt. Ich bin für Top-Down, du scheinbar nicht.

    Nach meiner Erfahrung funktionieren weder Top-Down noch Bottom-Up ordentlich.
    Ich programmiere meine Sachen eher nach Auftragen-Polieren.
    Wobei man natürlich trotzdem irgendwo anfangen muss, und da gehe ich die Sachen meist eher von unten an. Weil man damit schneller ausführbaren und vor allem testbaren Code hat.



  • BTT:
    Viele GUI Toolkits sind bereits Bridges. Diese dann nochmal zu Bridgen macht eher wenig Sinn.
    Also wieso einen eigenen Wrapper für Qt, Gtk und weiss-noch-was schreiben, wenn es Qt und Gtk doch eh schon für Windows, *NIX und OS-X gibt?

    Macht mMn. nur Sinn, wenn es für das Set an zu unterstützenden Plattformen nix gibt. Und dass das dann einen grossen Aufwand darstellt, sollte auch klar sein. Man muss sich bloss mal angucken wie gross die GUI Teile von Qt, Gtk etc. sind.

    Dann vielleicht eher gucken dass man die Software möglichst sauber trennt. Also möglichst die Controller-Logik nicht in die Window-Klassen reinhacken wie es sonst üblich ist, sondern diese wirklich in eigene Klassen raustrennen. Dann kann man zumindest Model und Controller gemeinsam lassen, und muss "nur" noch für jede Plattform (bzw. jedes GUI Toolkit) eigene Views schreiben.


Anmelden zum Antworten