Struktureller Programmaufbau (Objektorientierung)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
teddyt
User
Beiträge: 4
Registriert: Mittwoch 6. Dezember 2017, 12:16

Hi zusammen,

ich baue gerade eine kleine Applikation, habe aber bisher eher weniger Erfahrungen in der Objektorietierung.
Die Grundsätze sind klar, dennoch würde ich gerne Eure Empfehlung haben wie ich das Programm struktuerell am besten aufbauen kann.

Die Applikation hat eine grafische Oberfläche "MainDialog". Von dort aus wird die Hauptapplikation "App" gestartet und steuert den "Client" und den "Reader". Der Client sendet lediglich Anfragen an einen Server. Die Antworten werden durch den "Reader" verarbeitet.

Im Hauptdialog werden die Ergebnisse angezeigt. Wie löse ich am besten die Kommunikation sämtlicher Klassen mit dem Anzeigedialog?
Aktuell gebe ich "self" von der grafischen Oberfläche an alle Klassen weiter, damit diese Zugriff auf die Elemente haben.
Ist das sinnvoll oder sollte eher eine andere Struktur aufgebaut werden? Ich möchte mir nicht direkt alle Wege verbauen und benötige deshalb Euren Rat.

Code: Alles auswählen

class Client(EClient)
	# Sendet Daten an einen Server

class Reader(EReader)
	# Empfängt die Daten vom Server

class App(EClient, EReader)
	# Hauptapplikation

class MainDialog(QDialog)
	# Anzeigedialog
	a = App(self)
Vielen Dank,
Kai
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast eine unuebliche Anzahl von Vererbungen, und auch komische Praefixe. Die wuerde ich los werden. Ganz generell gilt, das man Komposition der Vererbung vorziehen sollte. Deine App wuerde also nicht von Reader und Client erben, sondern eine Instanz von jeweils dem einen und dem anderen ihr eigen nennen.

Ob diese Instanzen dann innerhalb der App *erzeugt* wird, oder per Konstruktor uebergeben, das ist eine Entwurfsentscheidung die von ein paar weiteren Randbedingungen abhaengt, zb Testbarkeit und Parametrisierung.

Wobei sogar die Frage zu stellen ist: warum reader und client? Die Kommunikation mit einem Server wuerde ich prinizpiell erstmal in *eine* Komponenten kapseln, und die App benutzt die dann.

Last but not least hast du in meinen Augen die Rolle von App und GUI verdreht. Die App erzeugt die GUI, nicht umgekehrt. Und verdrahtet die dann mit der reinen Kommunikations-Komponente.

In Summe also ungefaehr

Code: Alles auswählen

class Service:
    ...


class MainDialog(..):
    ...  


class App:

     def __init__(self, ...):
          self._gui = MainDialog()
          self._service = Service(...) # oder gleich als Argument
          self._connect_service_to_gui()
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@teddyt: kann mich __deets__ nur anschließen. Warum hast Du Lesen und Senden in zwei Klassen getrennt? Die Kommunikation gehört eigentlich zusammen. Was sind die Klassen EClient und EReader? Was bedeutet das E und warum gibt es überhaupt diese Vererbungstufe? Eine App ist ganz bestimmt kein EReader und auch kein EClient. Sie verwendet einen Client. MainDialog ist auch etwas seltsam. Ein Dialog ist meist nur eine Untereinheit, und es gibt ein MainWindow. Welche Funktion hat dabei die Klasse `App`?
Geschäftslogik sollte nichts von der GUI wissen müssen. Dass also Client eine Instanz auf GUI-Komponenten zugreift, ist nicht erlaubt.

Da Du QT verwendest, würde sich für die Kommunikation das Signal-Slot-Prinzip anbieten.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

teddyt hat geschrieben:Aktuell gebe ich "self" von der grafischen Oberfläche an alle Klassen weiter, damit diese Zugriff auf die Elemente haben.
Das ist technisch zwar möglich, aber konzeptionell meistens unsinnig. Die Logik regelt normalerweise ihre Aufgaben intern und gibt bestimmte Daten über eine API nach außen weiter. Wer sie wie benutzt und was damit angestellt wird, interessiert die Logik nicht. Die benutzenden Klassen (z.B. eine GUI) haben sich auf die Logik einzustellen und nicht umgekehrt. Das heißt, die müssen meine_logik.was_auch_immer() aufrufen und die weitere Verarbeitung übernehmen.

Etwas konkreter wäre das für die Anzeige von Text in einer GUI-Kompenente also sowas wie self.text_widget.set_text(self.client.get_info()), wobei get_info() natürlich einen konkreten Namen zum Einholen einer bestimmten Information hätte.
Sirius3 hat geschrieben:Welche Funktion hat dabei die Klasse `App`?
Würde mich auch interessieren. Möglicherweise reicht stattdessen eine einfache Funktion, die das Programm sozusagen anstößt:

Code: Alles auswählen

def main():
    app = MainWindow(Client())
    app.run()
Man muss die Dinge schließlich nicht unnötig komliziert machen, sofern das Ergebnis bei einer simpleren Lösung gleich ist.
teddyt
User
Beiträge: 4
Registriert: Mittwoch 6. Dezember 2017, 12:16

vielen Dank für Eure Antworten. Den Hinweis mit dem Signal-Slot Prinzip finde ich sehr gut. Kannte ich bisher noch nicht.

Ich verwende für den Client und Reader zwei unterschiedliche Klassen, weil die API es so vorgibt.
Hier mal ein Link: http://interactivebrokers.github.io/tws ... ml#connect

Ich habe mal versucht es noch etwas transparenter zu gestalten. Eine möglicher Aufbau könnte z.B. dann so aussehen.
Wobei die Daten über Signal-Slot ausgetauscht werden. Korrekt?

Code: Alles auswählen

class Logger:
    Daten aus dem Client und Reader entgegen nehmen
    Daten an die GUI übergeben

class Client:
    ...
    Daten vom Server anfragen
    Mögliche Fehlermeldungen an die Klasse Logger übergeben

class Reader (Läuft als Thread):
    ...
    Daten wurden vom Server empfangen.
    Fehlermedlungen an die Klasse Logger übergeben
    Daten an die GUI übergeben
    
class MainDialog(..):
    ...
    Sämtliche Daten anzeigen 
     
 class App:
     def __init__(self, ...):
     self._gui = MainDialog()
     self._client = Client(...)
     self._reader = Reader(...)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Puh, also die API hat's ja in sich... ich denke trotzdem, das du da nicht gezwungen bist, mehr als eine Klasse selbst zu erstellen. Das die dann ggf. von zwei anderen erbt mag sein, aber mehr sollte nicht notwendig sein. Falls doch, ist das halt eine Hilfsklasse, aber ultimativ solltest du einen Connector/Service haben, den wiederum die App benutzt. Und *die* leitet ganz bestimmt nicht von E-irgendwas ab.
teddyt
User
Beiträge: 4
Registriert: Mittwoch 6. Dezember 2017, 12:16

Stimmt, die API ist schon harter Tobak. Ich brauche nur einen winzigen Bruchteil davon. Das bekomme ich irgendie hin.

Das Hauptproblem was ich momentan noch habe, ist eine gute Lösung zu finden, damit die GUI mit den Klassen kommuniziert, d.h. ich brauche eine gescheite Entkoppelung. Das Signal-Slot-Prinzip schaue ich mir jetzt an. Ich hoffe das ist das richtige für mich.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du Qt verwendest, ganz sicher.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Der Logger ist wieder so ein Beispiel wie man es besser nicht machen sollte. Er sollte nicht an der GUI rumfummeln, sondern nur auf Fehler (und ggf Debug-Infos) lauschen. Falls das so gemeint ist, dass er zusätzlich zur GUI eine Ausgabe auf der Kommandozeile oder in einer Datei liefert, dann müssen eben GUI und Logger auf Fehler lauschen. Hier könnte man mit Qt ein failed-Signal basteln, welches Quelle und Details zum Fehler übermittelt.
teddyt
User
Beiträge: 4
Registriert: Mittwoch 6. Dezember 2017, 12:16

Der Logger soll eigentlich beides machen. Einmal alle Fehler, Warnungen und Informationen aus der Datenverbingung wie auch Verarbeitung sammeln, dann in eine Datei schreiben und zuätzlich auf der GUI in einerm Widget (QTextBrowser) ausgeben.
Antworten