Grundlegende Denkstrukturen

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
Daywalker7754
User
Beiträge: 1
Registriert: Mittwoch 2. Oktober 2019, 21:11

Hallo zusammen,

ich beschäftige mich seit Jahren immer einmal wieder mit Programmierung und nun mit Python. Ich „scheitere“ allerdings immer wieder bei dem Klassendesign. Ich verstehe die „einfachen“ Beispiele in allen Trainings, z.B. „class Student()“ und auch wenn etwas fertig ist und die Klassen in komplexen Programmen ausgestaltet sind.

Allerdings komme ich selbst nie darauf, wann ich eine Klasse schreibe und was ich dann in den Konstruktor packe, und was besser außerhalb bleibt.

Habt ihr generelle Regelungen im Kopf wie ihr euer Klassendesign ausbaut?

Meine Programme starten meist mit einem Skript das immer länger und länger wird. Dann erstelle ich meist Klassen für Import/Export wobei aber meine Inputs direkt im Konstruktor geladen werden (wahrscheinlich auch falsch). Dann trenne ich meist den Code Thematisch in Module, ohne Klassen. Sobald ich aber Klassen einbaue, verzweifle ich bei der Struktur und kreiere meist etwas was dann nicht mehr funktioniert.

Hierbei verzettle ich mich meist mit den folgenden Fragen:
- Was muss ein Konstruktor enthalten? Muss jede Klasse Variablen haben?
- Was mache ich mit dem Code der in einem Modul, wenn ich eine Klasse erstelle? Z.B. Logikflow um die Daten zu verändern und die einzelnen Methoden einer Klasse aufzurufen
- Haben Klassen nur Methoden und die gesamte Logik kommt in ein „Main“ Modul? Wenn ja, was mache ich wenn ich den Code auch hier logisch trennen will, da er sonst zu lang wird?

Geht es vielleicht noch jemandem so oder hatte vielleicht jemand auch einmal diese Schwierigkeiten?

Viele Grüße
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37
Allerdings komme ich selbst nie darauf, wann ich eine Klasse schreibe und was ich dann in den Konstruktor packe, und was besser außerhalb bleibt.
Vielleicht ist die folgende Regel gar nicht so verkehrt: wenn du darüber nachdenken musst, ob es eine Klasse sein könnte, ist es keine Klasse.
Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 Habt ihr generelle Regelungen im Kopf wie ihr euer Klassendesign ausbaut?
Meistens erkennt man die Komplexität eines Programms wenn man sich Zettel und Stift zur Hand nimmt, und einfach mal drauf los skizziert, welche Komponente implementiert wird, und wer mit wem sprechen muss etc. Python hat hier ggü. Sprachen wie Java den Vorteil, dass man selbst wählen kann ob man ein Programm Funktional, Objekt-Orientiert, oder in einem Mix aus beiden schreibt. Letzteres ist eher das was ich bei den meisten Projekten bisher gesehen habe. Und um OOP kommt man IMHO ab einer bestimmten Komplexität nicht mehr herum. Klassen bestehen ja immer aus Eigenschaften und Funktionalität. Es gibt Randfälle wo man einen simplen Namensraum für Daten benötigt, aber da hat Python ja allerhand schöne Konstrukte parat.
Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 - Was muss ein Konstruktor enthalten? Muss jede Klasse Variablen haben?
Du meinst ziemlich wahrscheinlich den Initializer, zu erkennen an der __init__-Methode. Das ist kein wirklicher Konstruktor wie man ihn aus Java kennt. Ersteres enthält i.d.R. Instanzattribute die im gesamten Namensraum der Klasse bekannt sind und der Klasse bei der Instanziierung (also dem Erstellen eines neuen Objektes jener Klasse) übergeben werden. Attribute sind Eigenschaften der Klasse. Möchte man aber auch Eigenschaften der Elternklasse bei Vererbung übernehmen, gibt es in der __init__-Methode noch die Besonderheit diese mittels super() zu "übernehmen".

Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 - Was mache ich mit dem Code der in einem Modul, wenn ich eine Klasse erstelle? Z.B. Logikflow um die Daten zu verändern und die einzelnen Methoden einer Klasse aufzurufen
Das verstehe ich nicht ganz. Wenn du Klassen in eigenen Modulen definiert hast die anderswo Verwendung finden sollen, werden sie dort importiert. "Logik" hast du übrigens überall. Geschäftslogik steckt üblicherweise in einer Main-Methode, sie dient bei Ausführungen / Start des Programms als Einstigespunkt, hier legst du also den Programmablauf (die Reihenfolge wann welches Objekt erstellt, welche Methode dieses Objektes aufgerufen wird etc.) fest.
Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 - Haben Klassen nur Methoden und die gesamte Logik kommt in ein „Main“ Modul? Wenn ja, was mache ich wenn ich den Code auch hier logisch trennen will, da er sonst zu lang wird?
Damit hast du ja quasi selbst schon deine zweite Frage fast beantwortet. Also ja. Und auch hier gilt: wird eine Methode / Funktion zu lang, unterteile sie an sinnvollen Stellen in weitere Methoden und Funktionen.

Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 Geht es vielleicht noch jemandem so oder hatte vielleicht jemand auch einmal diese Schwierigkeiten?
Ja, geht wohl jedem so. Einige hier und anderswo machen das schon so lange wie ich alt bin. Sowas lernt man halt nicht an Hand von fiktiven Beispielen einer Möbelschreinerei. Wenn man sich tiefer mit Objektorientierung beschäftigen möchte, sollte man sich die Grundlegenden Konzepte zu Objektorientierter Analyse, Design und Programmierung anschauen.

Mir hilft dazu das: https://www.tutorialspoint.com/object_o ... /index.htm
When we say computer, we mean the electronic computer.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Daywalker7754:
Die Klasse Student repräsentiert halt Eigenschaften und ggf simple Handlungsanweisungen (z.B. könnten die an Klausuren teilnehmen). Für solche Szenarien ist OOP ideal. Mir scheint es aber eher so als wenn du komplexe Logik in Klassen aufteilen willst. Das ist auch für "Profis" nicht immer so einfach.

Oftmals fährt man mit Funktionen besser, da sie das Problem sozusagen "direkter" angehen. Klassen kann man dann nehmen, wenn z.B. der erste Parameter für die Hälfte deiner Funktionen immer gleich ist. Dann würde man den einmal in der __init__() an die Klasseninstanz binden und in den einzelnen Methoden via self abrufen.

Ansonsten müsstest du schon konkreter werden, also mit Code zeigen, wo du deine Baustellen hast. Denn deine Fragestellung ist recht allgemein gehalten und kann somit auch nur mit Theorie beantwortet werden. Ich denke nicht, dass es dich auf diesem Level wirklich weiterbringen würde...
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 ich beschäftige mich seit Jahren immer einmal wieder mit Programmierung und nun mit Python. Ich „scheitere“ allerdings immer wieder bei dem Klassendesign. Ich verstehe die „einfachen“ Beispiele in allen Trainings, z.B. „class Student()“ und auch wenn etwas fertig ist und die Klassen in komplexen Programmen ausgestaltet sind.
Zwei Anmerkungen:

Objektorientierung ist ein Werkzeug zur Modellierung, Abstraktion und Strukturierung, aber m.M.n. keine unabdingbar Notwendigkeit. Man sollte sich finde ich überlegen, welche Gütekriterien man an "guten" Code anlegt, zum Beispiel Verständlichkeit, Wiederverwendbarkeit, Testbarkeit etc. Und wenn die Kriterien, die man für wichtig hält, erfüllt sind und der Code kommt ohne Klassen usw. aus, ist das meiner Meinung nach in Ordnung. Man sollte nicht denken, dass Python-Code ohne Klassen kein vollwertiger Code ist.

Ich stelle mir Klassen immer so vor, dass sie dazu dienen, Daten und Methoden auf diesen gemeinsam zu kapseln. Ich habe zum Beispiel häufig Code in strukturierten Programmiersprache ohne Klassen gesehen, wo Datenstrukturen wie Listen oder Stacks so implementiert wurden, dass es eine Reihe von Routinen (zum Beispiel 'append', 'delete', 'push', 'pop', etc.) gab, die alle als Parameter dieselbe Variable bekamen und diese modifizierten. Das ist im Prinzip OOP ohne Klassen. Und sowas würde man in Python dann halt mit Klassen abbilden. Klassen, die nur Werte kapseln, ohne irgendwelche Operationen auf ihnen zu implementieren, die also keinen Mehrwert gegenüber einem Dictionary o.Ä. bieten, oder die nur Methoden enthalten, die sich nicht auf das Objekt beziehen (die also Static-Methods sein könnten), finde ich nicht so sinnvoll.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

@sls: Ich finde ein paar deiner Aussagen etwas problematisch:
sls hat geschrieben: Sonntag 6. Oktober 2019, 07:19
Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 - Was muss ein Konstruktor enthalten? Muss jede Klasse Variablen haben?
Du meinst ziemlich wahrscheinlich den Initializer, zu erkennen an der __init__-Methode. Das ist kein wirklicher Konstruktor wie man ihn aus Java kennt. Ersteres enthält i.d.R. Instanzattribute die im gesamten Namensraum der Klasse bekannt sind und der Klasse bei der Instanziierung (also dem Erstellen eines neuen Objektes jener Klasse) übergeben werden. Attribute sind Eigenschaften der Klasse.
Die Unterscheidung zwischen Konstruktor und Initialisator ist zwar formal richtig. Umgangsprachlich wuerde ich das aber Konstruktor nennen, weil man einen echten Konstruktor - also ein Stueck Code, dass ueberhaupt erstmal den Speicher fuer ein Objekt bereitstellt - sehr selten braucht. Doch hier liegst du IMHO auch falsch: gerade in Java ist das genau NICHT anders. In C++ und auch in Python kann ich auch da Kontrolle uebernehmen, zb mit dem new-operator bzw. __new__ in Python. Java erlaubt das genau nicht, und hat darum eben auch einen Initialisator, wenn man dieser Unterscheidung wie du sie (richtig) machst folgt.

Und die Antwort auf die zweite Frage ist ganz klar: ja, muss sie. Eine Klasse ohne Zustand ist nichts als eine Reihe von Funktionen, und die kann man in Python auch einfach so hinschreiben, und braucht den Ueberhang einer Klasse genau dann nicht.
sls hat geschrieben: Sonntag 6. Oktober 2019, 07:19
Daywalker7754 hat geschrieben: Samstag 5. Oktober 2019, 21:37 - Was mache ich mit dem Code der in einem Modul, wenn ich eine Klasse erstelle? Z.B. Logikflow um die Daten zu verändern und die einzelnen Methoden einer Klasse aufzurufen
Das verstehe ich nicht ganz. Wenn du Klassen in eigenen Modulen definiert hast die anderswo Verwendung finden sollen, werden sie dort importiert. "Logik" hast du übrigens überall. Geschäftslogik steckt üblicherweise in einer Main-Methode, sie dient bei Ausführungen / Start des Programms als Einstigespunkt, hier legst du also den Programmablauf (die Reihenfolge wann welches Objekt erstellt, welche Methode dieses Objektes aufgerufen wird etc.) fest.
Auch diese Aussage (das Logik in main waere) finde ich problematisch. Main schnuert die diversen Komponenten zusammen, aber gross Logik ist da eher nicht drin. Wo die genau lebt haengt vom Einzelfall ab, aber eigentlich selten bis nie ist sie in der main-Funktion. Und gerade Geschaeftslogik kann zB in der Klassenhierarchie und dem resultierenden Objekt-Graphen eines Domaenen-Modells stecken.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Kritik zur Geschäftslogik in main() sehe ich ähnlich. Eigentlich passiert da nur relativ wenig. Oft hat man print()-Aufrufe drin, sowie evtl Exception-Handling auf der obersten Ebene, um Fehlermeldungen ausgeben zu können. Und klar, sie wird per Konvention beim Programmstart aufgerufen und stößt den weiteren Ablauf an, aber die Programmstruktur wird damit ja trotzdem nicht definiert. Nur weil ich weiß, was ich tun muss, um das Auto anzulassen, habe ich noch lange nicht der Verlauf der gesamten Fahrt festgelegt. ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Daywalker7754: Man kann durchaus anfangen ein Programm ohne Klassen zu entwickeln. Wenn man das sauber macht wird man in der Regel früher oder später Funktionen mit ”zu vielen” Argumenten haben oder Daten die man zusammen in Datenstrukturen stecken muss, also mindestens mal `collections.namedtupel` herum reichen wenn das nicht zu unübersichtlich werden soll. Und falls man da dann neben den Daten die zu einem `namedtupel`-Typ zusammengefasst werden, auch noch Operationen findet die dazu gehören, hat man ja bereits alles für eine Klasse beisammen.

In die `__init__()` sollte möglichst wenig, damit man Objekte von dem Typ möglichst einfach und unkompliziert erstellen kann. Für komplizierteres bieten sich Klassenmethoden an. Beispielsweise Parsen oder Laden aus externen Quellen. So etwas will man üblicherweise nicht in der `__init__()` haben, weil es dann komplizierter wird Tests zu schreiben.

Eine Klasse muss in aller Regel mindestens eine ”Variable” haben, wobei *eine* in der Regel nur ausreicht wenn die Klasse diesen einen Wert in irgendeiner Weise ”wrapped” um eine andere Schnittstelle zu bieten als der Typ eigentlich hat. Ansonsten sind Klassen in der Regel dazu da um mehr als einen Wert zusammenzufassen. Entweder mehrere Werte mit Namen und Bedeutung oder eine Sammlung von Objekten bei Containertypen.

Ich persönlich habe mittlerweile bei fast allen Projekten mit Klassen das externe `attr`-Modul als Abhängigkeit.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

@__deets__: mit dem Konstruktor fehlt mir die Erfahrung, außer bei Vererbung eines namedtuple habe ich auch noch nie ein realistisches Beispiel gesehen, in dem ich den __new__-Operator brauche. Ich nehme mir deine Kritik zu Herzen, danke für die Klarstellung.
When we say computer, we mean the electronic computer.
Antworten