Ein konkretes Bsp wäre gut.
Ich habe den Vergleich mit einem Kuchen gelesen. Gut gemeint, aber iwie ist mir das immer noch nicht klar.
Eine def kann ja auch von überall her aufgerufen werden. Eine class doch auch. Oder wird die ausgeführt, wenn das Programm da vorbei kommt (eine def muss ja iwo aufgerufen werden)?
Übernahme der Eingabefelder
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
Eine Klasse kann Zustand haben. Als Freund von Elektronik ist dir vielleicht dieses Beispiel eingängig: eine Funktion (so heißt das, def ist nur ein Schlüsselwort) zb gut geeignet, einen ohmschen Widerstand zu modellieren. Eingabe sind zb Spannung und Widerstand, und geliefert wird der fließende Strom.
Ein Kondensator hingegen hat einen Widerstand, der von der im laufe der Zeit akkumulierten Ladung abhängt. Diese Ladung kann eine Funktion nicht speichern. Eine Klasse kann sich den aber merken, und zb über die Zeit akkumulieren.
Ein Kondensator hingegen hat einen Widerstand, der von der im laufe der Zeit akkumulierten Ladung abhängt. Diese Ladung kann eine Funktion nicht speichern. Eine Klasse kann sich den aber merken, und zb über die Zeit akkumulieren.
Ah, dann werden die Variablen-Werte in der Klasse gespeichert?
Die Variablen sind dann ausserhalb der Klasse nicht "belegt" (wie eine globale Variable)?
Und eine Klasse wird auch erst beim Aufruf von dieser ausgeführt. Also nicht dann, wenn der Interpreter da vorbei kommt?
Die Variablen sind dann ausserhalb der Klasse nicht "belegt" (wie eine globale Variable)?
Und eine Klasse wird auch erst beim Aufruf von dieser ausgeführt. Also nicht dann, wenn der Interpreter da vorbei kommt?
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
Die Klasse wird schon direkt ausgeführt. Erst danach existiert sie. Eine Blaupause muss man auch erstmal zeichnen. Und das gilt auch für eine Funktion. Die muss auch erstmal definiert werden. Darum heißt das ja auch “def blabla”. Vorher gibt’s auch kein blabla.
Aber danach werden eben wahlweise Aufrufe ( bei Funktionen) oder Instanzen (bei Klassen) erstellt. Und mit letzteren arbeitet man dann eben länger.
Aber danach werden eben wahlweise Aufrufe ( bei Funktionen) oder Instanzen (bei Klassen) erstellt. Und mit letzteren arbeitet man dann eben länger.
Also wird die Klasse auch erst ausgeführt, wenn diese aufgerufen.
Also kann diese oben im Code stehen und da wird die erst definiert, aber noch nicht ausgeführt. Richtig?
Und das mit den Variablen (siehe oben)?
Also kann diese oben im Code stehen und da wird die erst definiert, aber noch nicht ausgeführt. Richtig?
Und das mit den Variablen (siehe oben)?
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
Eine Klasse ist ein Bauplan für ein neues Objekt. Man nennt so ein neues Objekt auch Instanz einer Klasse. Es kann beliebig viele Instanzen, die nach dem Bauplan einer Klasse "gebaut" wurden geben.
Jede Instanz ist zwar nach dem Bauplan gebaut, verwaltet aber ihrer zugehörigen Daten separat von allen anderen Instanzen. (Mit Ausnahmen)
Bei Objekten also Instanzen von Klassen spricht man in der Regel nicht davon, dass sie ausgeführt werden.
Beipiel Taschenrechner:
Jede Instanz ist zwar nach dem Bauplan gebaut, verwaltet aber ihrer zugehörigen Daten separat von allen anderen Instanzen. (Mit Ausnahmen)
Bei Objekten also Instanzen von Klassen spricht man in der Regel nicht davon, dass sie ausgeführt werden.
Beipiel Taschenrechner:
Code: Alles auswählen
class TaschenrechnerBauplan:
def __init__(self):
"""
Initialisierung des Objekts mit eine Instanzvariablen Register
self ist eine Referenz auf die jeweilige Instanz
"""
self.register = 0
def load(self, zahl):
""" Instanzmethode (Funktion) zum laden einer Zahl ins Register"""
self.register = zahl
def addition(self, zahl):
""" Instanzmethode zum Addieren einer Zahl zum Register"""
self.register = self.register + zahl
def ausgabe(self):
""" Instanzmethode zur Ausgabe des Registers"""
print(self.register)
# Bauen der Instanz "taschenrechner_instanz":
taschenrechner_instanz = TaschenrechnerBauplan()
# Benutzen der Instanz "taschenrechner_instanz"
taschenrechner_instanz.load(1)
taschenrechner_instanz.addition(2)
taschenrechner_instanz.ausgabe()
"""
Ausgabe:
3
"""
Gutes Bsp. Danke
Was bringt das aber nun, weil ja die load, addition und ausgabe Funktionen direkt aufgerufen werden könnten?
Ich sehe, dass einzig die Variable register gespeichert bleibt (der Wert).
Also ist das vorallem eine Möglichkeit, globale Variablen zu vermeiden?
__init__ wird nur ausgeführt, wenn kein Wert an die Klasse TaschenrechnerBauplan übergeben wird?
Was bringt das aber nun, weil ja die load, addition und ausgabe Funktionen direkt aufgerufen werden könnten?
Ich sehe, dass einzig die Variable register gespeichert bleibt (der Wert).
Also ist das vorallem eine Möglichkeit, globale Variablen zu vermeiden?
__init__ wird nur ausgeführt, wenn kein Wert an die Klasse TaschenrechnerBauplan übergeben wird?
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
__inti__ wird direkt nach der Erzeugung aufgerufen. Ob da Argumente gebraucht werden oder nicht, hängt von der Klasse ab.
Und ja, es geht um Zustand. Globaler Zustand ist bestenfalls unübersichtlich. Schlimmstenfalls Quelle von Fehlern.
Und ja, es geht um Zustand. Globaler Zustand ist bestenfalls unübersichtlich. Schlimmstenfalls Quelle von Fehlern.
Vielen Dank.
Ich werde mir auf jeden Fall den Link zu deinem Bsp notieren.
Jetzt blicke ich besser dahinter.
Ich werde mir auf jeden Fall den Link zu deinem Bsp notieren.
Jetzt blicke ich besser dahinter.
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
- __blackjack__
- User
- Beiträge: 13100
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Also mir gefällt ja das Taschenrechnerbeispiel nicht so wirklich. Von Funktionen kommend würde das so nicht entstehen, weil diese Funktionen so niemand schreiben würde. Man muss also OOP schon drauf haben um das so zu schreiben und es gibt keinen ”Schritt” von Nicht-OOP zu OOP, den man da nachvollziehen könnte. ”Historisch” ist das ja eher so das Objekte aus einem Verbunddatentyp (``RECORD`` in Pascal, ``TYPE`` in BASIC, ``struct`` in C) entstanden sind. Der OP kommt ja aus einer Sprache mit GOTO-Anweisung.
Sinnvoller aus der Ecke kommend ist IMHO eher ein Beispiel wo Funktionen verwendet werden, da aber mittlerweile zu viele Einzelwerte herum gereicht werden müssen, so dass man die sinnvoll zu einem Verbunddatentyp zusammenfassen kann/sollte, und wo man dann einen der Vorteile zeigen könnte die dadurch entstehen, dass man die Funktionen die darauf operieren mit in den Datentyp hineinzieht. Das wäre also Polymorphie als einfachster Vorteil. Blöderweise braucht man in Python schon Klassen wenn man das gar nicht braucht, weil das der einzige Verbunddatentyp ist. Andererseits macht es natürlich auch Sinn dafür keinen eigenen Typ einzuführen, wenn Klassen ja auch diese „plain old data“ (POD)/passive Datenstrukturen abdecken können, und letztlich in Python ja *jeder* Datentyp durch eine Klasse repräsentiert wird, ja werden muss.
OOP-Beispiele haben in der Regel das Problem, dass sie entweder sehr künstlich sind, und sich der Anfänger fragt, warum macht man dass, oder sie sind schon recht komplex, und überfordern damit Anfänger. Was in der Natur der Sache liegt, weil die Aufgabe OOP ist Komplexität zu organisieren und handhabbar zu halten. Wenn in dem Beispiel keine Polymorphie oder Vererbung vorkommt, dann ist es IMHO kein OOP-Beispiel, denn das könnte man dann auch genau so gut ohne OOP lösen.
Zurück zum Taschenrechnerbeispiel, das man in QBasic beispielsweise mit Verbunddatentyp und Prozeduren so schreiben würde:
Das sieht so schon nicht sinnvoll aus, und das auf Pseudo-OOP zu erweitern taugt IMHO nicht jemandem den Sinn von OOP näher zu bringen. Das hat keinen wirklichen Vorteil gegenüber:
Und das könnte man noch weiter vereinfachen.
Sinnvoller aus der Ecke kommend ist IMHO eher ein Beispiel wo Funktionen verwendet werden, da aber mittlerweile zu viele Einzelwerte herum gereicht werden müssen, so dass man die sinnvoll zu einem Verbunddatentyp zusammenfassen kann/sollte, und wo man dann einen der Vorteile zeigen könnte die dadurch entstehen, dass man die Funktionen die darauf operieren mit in den Datentyp hineinzieht. Das wäre also Polymorphie als einfachster Vorteil. Blöderweise braucht man in Python schon Klassen wenn man das gar nicht braucht, weil das der einzige Verbunddatentyp ist. Andererseits macht es natürlich auch Sinn dafür keinen eigenen Typ einzuführen, wenn Klassen ja auch diese „plain old data“ (POD)/passive Datenstrukturen abdecken können, und letztlich in Python ja *jeder* Datentyp durch eine Klasse repräsentiert wird, ja werden muss.
OOP-Beispiele haben in der Regel das Problem, dass sie entweder sehr künstlich sind, und sich der Anfänger fragt, warum macht man dass, oder sie sind schon recht komplex, und überfordern damit Anfänger. Was in der Natur der Sache liegt, weil die Aufgabe OOP ist Komplexität zu organisieren und handhabbar zu halten. Wenn in dem Beispiel keine Polymorphie oder Vererbung vorkommt, dann ist es IMHO kein OOP-Beispiel, denn das könnte man dann auch genau so gut ohne OOP lösen.
Zurück zum Taschenrechnerbeispiel, das man in QBasic beispielsweise mit Verbunddatentyp und Prozeduren so schreiben würde:
Code: Alles auswählen
TYPE TTaschenrechner
register AS INTEGER
END TYPE
SUB TaschenrechnerInit (rechner AS TTaschenrechner)
rechner.register = 0
END SUB
SUB TaschenrechnerLadeRegister (rechner AS TTaschenrechner, zahl AS INTEGER)
rechner.register = zahl
END SUB
SUB TaschenrechnerAddiere (rechner AS TTaschenrechner, zahl AS INTEGER)
rechner.register = rechner.register + zahl
END SUB
SUB TaschenrechnerAusgabe (rechner AS TTaschenrechner)
PRINT rechner.register
END SUB
DIM rechner AS TTaschenrechner
TaschenrechnerInit rechner
TaschenrechnerLadeRegister rechner, 1
TaschenrechnerAddiere rechner, 2
TaschenrechnerAusgabe rechner
Code: Alles auswählen
DIM register AS INTEGER
register = 0
register = 1
register = register + 2
PRINT register
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Verstehe nur Bahnhof.
Und was ist OOP?
Und was ist OOP?
Wenn es mehrere Möglichkeiten gibt, eine Aufgabe zu erledigen, und eine davon in einer Katastrophe endet oder sonstwie unerwünschte Konsequenzen nach sich zieht, dann wird es jemand genau so machen. Alles, was schiefgehen kann, wird auch schiefgehen.
(Murphys Gesetz)
(Murphys Gesetz)
OOP steht für Objekt Orientierte Programmierung
Die Idee dahinter ist, dass man zum Beispiel den Taschenrechner im Programm als Objekt mit eigenen Eigenschaften (hier nur das Register) und eigenen Funktionen (Laden, Addieren Ausgeben) modelliert.
Dem gegenüber steht die Funktionale oder Prozedurale Programmierung.
Wie du ja schon gemerkt hattest, ist es nicht zwingend erforderlich den Taschenrechner als Klasse zu definieren. Die gleiche Funktionalität hätte man auch mit einfachen Funktionen darstellen können.
Manche Programmiersprachen sind so aufgebaut, dass sie ausschließlich der Objekt Orientierten Programmierung folgen. Andere sind rein Funktional. Bei Python ist jede beliebige Mischung aus beiden Paradigmen möglich.
Die Idee dahinter ist, dass man zum Beispiel den Taschenrechner im Programm als Objekt mit eigenen Eigenschaften (hier nur das Register) und eigenen Funktionen (Laden, Addieren Ausgeben) modelliert.
Dem gegenüber steht die Funktionale oder Prozedurale Programmierung.
Wie du ja schon gemerkt hattest, ist es nicht zwingend erforderlich den Taschenrechner als Klasse zu definieren. Die gleiche Funktionalität hätte man auch mit einfachen Funktionen darstellen können.
Manche Programmiersprachen sind so aufgebaut, dass sie ausschließlich der Objekt Orientierten Programmierung folgen. Andere sind rein Funktional. Bei Python ist jede beliebige Mischung aus beiden Paradigmen möglich.
@_blackjack_
ich hoffe ich lehne mich nicht zu weit aus dem Fenster.
Der am einfachsten zu verstehende Aspekt bei Klassen - nämlich Datenkapselung bzw. der Schnittstellen-Gedanke ist im Rahmen von Python vermutlich aufgrund der Abwesenheit von private für einen Anfänger nicht gut erkennbar.
Damit bleibt noch Polymorphie und die Verbundthematik, die Du angesprochen hast.
Das Buch "Clean Code" fand ich diesbezüglich sehr hilfreich. Die Codebeispiele sind allerdings Java.
Hinsichtlich Verbundthematik gibt's dort einen Passus, in dem es z.B. heißt, dass Methoden mit mehr als zwei Übergabeparametern Refactoring-bedürftig sind:
--> hier sollte man eben aus spam, ham und eggs eine Klasse machen und dann das entsprechende Objekt übergeben
ich hoffe ich lehne mich nicht zu weit aus dem Fenster.
Der am einfachsten zu verstehende Aspekt bei Klassen - nämlich Datenkapselung bzw. der Schnittstellen-Gedanke ist im Rahmen von Python vermutlich aufgrund der Abwesenheit von private für einen Anfänger nicht gut erkennbar.
Damit bleibt noch Polymorphie und die Verbundthematik, die Du angesprochen hast.
Das Buch "Clean Code" fand ich diesbezüglich sehr hilfreich. Die Codebeispiele sind allerdings Java.
Hinsichtlich Verbundthematik gibt's dort einen Passus, in dem es z.B. heißt, dass Methoden mit mehr als zwei Übergabeparametern Refactoring-bedürftig sind:
Code: Alles auswählen
foo = bar(spam, ham, eggs)
Das wäre dann eine Datenklasse, die ja auch die Methode "bar" enthalten könnte. So etwas gibt es natürlich auch in Python und das macht sicher auch in manchen Fällen Sinn.--> hier sollte man eben aus spam, ham und eggs eine Klasse machen und dann das entsprechende Objekt übergeben
Aber dass man ab drei Übergabeparametern, diese schon in eine Klasse zusammenfassen soll würde ich nicht pauschal als "Clean Code" bezeichnen. Das hängt sicher immer stark von der Situation ab.
Das halte ich fur fast schon groben Unfug. Es ist überhaupt nichts gewonnen, 3 Argumente durch eine Klasse mit 3 Attributen zu ersetzen. Außer es für die Programmiererin komplizierter zu machen.Buchfink hat geschrieben: ↑Sonntag 10. Oktober 2021, 20:31 Hinsichtlich Verbundthematik gibt's dort einen Passus, in dem es z.B. heißt, dass Methoden mit mehr als zwei Übergabeparametern Refactoring-bedürftig sind:--> hier sollte man eben aus spam, ham und eggs eine Klasse machen und dann das entsprechende Objekt übergebenCode: Alles auswählen
foo = bar(spam, ham, eggs)
Genau solche formulaischen Vorgehensweisen, schlimmstenfalls noch erzwungen durch Code-Metrik-Tools, sind eine der dunklen Ecken der Softwareentwicklung. Da wird dann ohne Sinn und Verstand etwas verkompliziert oder angekreidet.
- __blackjack__
- User
- Beiträge: 13100
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Buchfink: Ich verstehe nicht so recht warum man dafür Zugriffsschutz brauchen soll. Zur Datenkapselung braucht man das nicht. Dazu reicht es wenn man einfach nicht auf Sachen zugreift, die zu Interna gehören. Die kann man einfach mit einem Unterstrich als solche kenntlich machen. Und in vielen einfachen Einstiegsbeispielen gibt es dann in Sprachen wie Java triviale Getter und Setter, womit die Attribute de fakto doch wieder öffentlich sind. Das ist dann keine Kapselung beziehungsweise ist das nichts anderes als in Python direkt auf Attribute zuzugreifen. Die Argumentation bei Java ist dann, dass man die Interna beim Zugriff jederzeit ändern kann, was beim direkten Zugriff nicht geht. Das Argument zieht aber bei Python nicht, weil Python berechnete Attribute kennt, man da also nicht auf ”Vorrat” triviale Getter/Setter schreiben muss, nur für den Fall, dass man da vielleicht irgendwann einmal etwas anders machen will.
Ich sehe das auch nicht als *den* Vorteil/Grund von Klassen, denn das kann man beispielsweise in C auch durch Strukturen erreichen wo man dem Benutzer der Bibliothek grundsätzlich einfach nur einen void-Zeiger auf eine opake Datenstruktur in die Hand gibt, und dadurch auch in C leicht erzwingen kann, dass die Daten nur über die dafür passenden Funktionen manipuliert werden können.
Selbst wenn man in C auch die ``struct``-Deklaration öffentlich macht, aber Funktionen zur Manipulation zur Verfügung stellt, ist das immer noch Datenkapselung wenn die Dokumentation deutlich macht, dass man nicht direkt in diesen Strukturen herumpfuschen soll.
Datenkapselung habe ich das erste mal mit Pascal beigebracht bekommen, und zwar mit klassischem Pascal mit RECORD und Units, ohne OBJECT oder CLASS,. ”OOP” habe ich auch das erste mal mit Pascal gezeigt bekommen — und nicht verstanden warum das jetzt besser sein sollte, weil mir in dem Zusammenhang nichts gezeigt wurde, was das wirklich besser gemacht hat, als die prozedurale Lösung, die wir davor für das gestellte Problem entwickelt hatten. Also keine Polymorphie oder Vererbung. Es war halt einfach nur der selbe Code mit dem kleinen syntaktischen Unterschied, dass das erste Argument aus der Parameterliste verschwunden ist, und beim Aufruf vor den Prozedur- oder Funktionsnamen gerutscht ist, und innerhalb der Prozedur/Funktion den festen Namen `Self` und quasi so etwas wie ein ``WITH Self DO`` für den Körper der Prozedur/Funktion gilt.
Wirklich einen einfachen Vorteil erhält man IMHO erst durch Polymorphie, weil dadurch Klassen/Implementierungen von Objekten austauschbar werden, solange sie die Schnittstelle erfüllen.
In Python ist da wahrscheinlich `__str__()` eines der einfachsten Beispiele, weil selbst bei POD-Klassen oft eine eigene Zeichenkettendarstellung Sinn macht, und `print()` eine sehr einfache und zu dem Zeitpunkt sicher bekannte Funktion ist, die das dann nutzen kann, dass man eine für den Benutzer sinnvolle Darstellung als Zeichenkette ausgeben kann, in dem man ganz einfach das Objekt an `print()` übergibt.
Das mit der Argumentanzahl steht so hart nicht in „Clean Code“. Da steht das einmal hinten im Zusammenfassungskapitel „Smells und Heuristiken“, was ja schon mal keine harten Regeln sind. Ob man dann aus drei Argumenten *ein* Objekt machen sollte, kommt auch *sehr* darauf an ob das überhaupt Sinn macht die zusammenzufassen. Im Kapitel über Funktionen ist da ein gutes Beispiel mit einer `makeCircle()`-Funktion die `x`, `y`, und `radius` als Argumente bekommt und wo sich `x` und `y` als `Point` zusammenfassen lassen. Es würde eher keinen Sinn machen da auch noch `radius` mit rein zu nehmen. Denn dann würde man `makeCircle()` wahrscheinlich schon genau die Datenstruktur übergeben, die man mit dieser Funktion eigentlich erstellen möchte.
Ich persönlich werde ”erst” bei mehr als 5 bis 6 Argumenten ”hellhörig”. Die `makeCircle()`-Argumente hätte ich nicht wegen der Anzahl der Argumente zusammengefasst, sondern weil in so einem Programm ziemlich sicher an vielen Stellen auf Punkten operiert wird, und es deshalb Sinn macht die Koordinaten zu einem Objekt zusammenzufassen.
Die Argumentanzahl generell so drastisch zu reduzieren wie das von Martin vorgeschlagen wird, verschiebt das Problem IMHO auch nur zu immer mehr und immer verschachtelteren Argument-Objekten.
Mit Büchern zu statisch typisierten Sprachen wäre ich bei Python (und anderen dynamischen Sprachen) vorsichtig. Und wenn einem ein Buch etwas über objektorientierte Programmierung beibringen will und dafür Java (oder C++) verwendet, wäre ich auch vorsichtig. Man muss da immer aufpassen was tatsächlich allgemein für OOP gilt, und was nur die eingeschränkte(re) Sicht der jeweiligen Sprache auf das Thema ist.
Ich sehe das auch nicht als *den* Vorteil/Grund von Klassen, denn das kann man beispielsweise in C auch durch Strukturen erreichen wo man dem Benutzer der Bibliothek grundsätzlich einfach nur einen void-Zeiger auf eine opake Datenstruktur in die Hand gibt, und dadurch auch in C leicht erzwingen kann, dass die Daten nur über die dafür passenden Funktionen manipuliert werden können.
Selbst wenn man in C auch die ``struct``-Deklaration öffentlich macht, aber Funktionen zur Manipulation zur Verfügung stellt, ist das immer noch Datenkapselung wenn die Dokumentation deutlich macht, dass man nicht direkt in diesen Strukturen herumpfuschen soll.
Datenkapselung habe ich das erste mal mit Pascal beigebracht bekommen, und zwar mit klassischem Pascal mit RECORD und Units, ohne OBJECT oder CLASS,. ”OOP” habe ich auch das erste mal mit Pascal gezeigt bekommen — und nicht verstanden warum das jetzt besser sein sollte, weil mir in dem Zusammenhang nichts gezeigt wurde, was das wirklich besser gemacht hat, als die prozedurale Lösung, die wir davor für das gestellte Problem entwickelt hatten. Also keine Polymorphie oder Vererbung. Es war halt einfach nur der selbe Code mit dem kleinen syntaktischen Unterschied, dass das erste Argument aus der Parameterliste verschwunden ist, und beim Aufruf vor den Prozedur- oder Funktionsnamen gerutscht ist, und innerhalb der Prozedur/Funktion den festen Namen `Self` und quasi so etwas wie ein ``WITH Self DO`` für den Körper der Prozedur/Funktion gilt.
Wirklich einen einfachen Vorteil erhält man IMHO erst durch Polymorphie, weil dadurch Klassen/Implementierungen von Objekten austauschbar werden, solange sie die Schnittstelle erfüllen.
In Python ist da wahrscheinlich `__str__()` eines der einfachsten Beispiele, weil selbst bei POD-Klassen oft eine eigene Zeichenkettendarstellung Sinn macht, und `print()` eine sehr einfache und zu dem Zeitpunkt sicher bekannte Funktion ist, die das dann nutzen kann, dass man eine für den Benutzer sinnvolle Darstellung als Zeichenkette ausgeben kann, in dem man ganz einfach das Objekt an `print()` übergibt.
Das mit der Argumentanzahl steht so hart nicht in „Clean Code“. Da steht das einmal hinten im Zusammenfassungskapitel „Smells und Heuristiken“, was ja schon mal keine harten Regeln sind. Ob man dann aus drei Argumenten *ein* Objekt machen sollte, kommt auch *sehr* darauf an ob das überhaupt Sinn macht die zusammenzufassen. Im Kapitel über Funktionen ist da ein gutes Beispiel mit einer `makeCircle()`-Funktion die `x`, `y`, und `radius` als Argumente bekommt und wo sich `x` und `y` als `Point` zusammenfassen lassen. Es würde eher keinen Sinn machen da auch noch `radius` mit rein zu nehmen. Denn dann würde man `makeCircle()` wahrscheinlich schon genau die Datenstruktur übergeben, die man mit dieser Funktion eigentlich erstellen möchte.
Ich persönlich werde ”erst” bei mehr als 5 bis 6 Argumenten ”hellhörig”. Die `makeCircle()`-Argumente hätte ich nicht wegen der Anzahl der Argumente zusammengefasst, sondern weil in so einem Programm ziemlich sicher an vielen Stellen auf Punkten operiert wird, und es deshalb Sinn macht die Koordinaten zu einem Objekt zusammenzufassen.
Die Argumentanzahl generell so drastisch zu reduzieren wie das von Martin vorgeschlagen wird, verschiebt das Problem IMHO auch nur zu immer mehr und immer verschachtelteren Argument-Objekten.
Mit Büchern zu statisch typisierten Sprachen wäre ich bei Python (und anderen dynamischen Sprachen) vorsichtig. Und wenn einem ein Buch etwas über objektorientierte Programmierung beibringen will und dafür Java (oder C++) verwendet, wäre ich auch vorsichtig. Man muss da immer aufpassen was tatsächlich allgemein für OOP gilt, und was nur die eingeschränkte(re) Sicht der jeweiligen Sprache auf das Thema ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
@_deets_ @rogerb @_blackjack_
Hui, mit so vielen interessanten Reaktionen hatte ich jetzt gar nicht gerechnet. Ich wollte allerdings gar nicht den ursprünglichen Thread "sprengen".
Eigentlich habe ich nur schriftlich überlegt, wie man den Sinn und Zweck von Klassen erklärt - und zwar ohne das Thema "Software-Architektur" zu beleuchten.
Die Schnittstellen-Thematik ist imho der am einfachsten zu verstehende Aspekt von Klassen.
Das was sie architektonisch ermöglichen (z.B. Polymorphie), ist leider wie @_blackjack_ schon anmerkte
mit den meisten (Trivial-)Beispielen nicht wirklich gut vermittelbar. (Thorngates Postulat der angemessenen Komplexität)
@_deets_
Im Sinne von "Shu Ha Ri" ist es aber hin und wieder hilfreich, als Anfänger Regeln/Konventionen erst mal zu verstehen, bevor man sie bricht.
@_blackjack_
Offensichtlich kann man das aber auch per Konvention sicherstellen. Leider habe ich diesbezüglich noch keine persönlichen Erfahrungen machen können. Aber nun, das kann ja noch werden, denke ich.
Was ich aber durchaus sinnvoll finde, ist z. B. nur eine lesenden Beschränkung (sprich "nur" Getter) zu ermöglichen (je nach Semantik natürlich)
Das muss man aber als bewußte Design-Entscheidung treffen. Eine "Voratshaltung" von Code ist selbstredend fast nie sinnvoll.
RECORDS ziehe ich dann eine CLASS vor, wenn ich absehbar keine Polymorphie brauche und wenn sich dadurch z.B. Performenceverbesserungen ergeben.
RECORDS sind auch etwas einfacher in der Handhabung im Hinblick auf Speichermanagment (zumindest in Delphi)
Ich bin auch kein OOP-Hardliner im Sinne von "nur OOP ist das einzig wahre".
Imho konnte OOP in vielen Aspekten seine Versprechen nicht wirklich einhalten. Aber das dürfte eine andere Diskussion sein #
Ich weiß nicht, ob ich mich trauen sollte, die hier aufzumachen.
Da ist man dann mit einer Klasse (oder einem Record) durchaus "besser" dran.
Letzlich hängt es auch davon ab, wie stabil eine Parameterliste potentiell ist. Bei dem Beispiel mit dem Kreis kann man natürlich davon ausgehen, dass das perspektivisch stabil bleibt.
Hui, mit so vielen interessanten Reaktionen hatte ich jetzt gar nicht gerechnet. Ich wollte allerdings gar nicht den ursprünglichen Thread "sprengen".
Eigentlich habe ich nur schriftlich überlegt, wie man den Sinn und Zweck von Klassen erklärt - und zwar ohne das Thema "Software-Architektur" zu beleuchten.
Die Schnittstellen-Thematik ist imho der am einfachsten zu verstehende Aspekt von Klassen.
Das was sie architektonisch ermöglichen (z.B. Polymorphie), ist leider wie @_blackjack_ schon anmerkte
mit den meisten (Trivial-)Beispielen nicht wirklich gut vermittelbar. (Thorngates Postulat der angemessenen Komplexität)
@_deets_
Natürlich muss man das situationsbezogen anwenden. Wenn es z. B. semantisch nicht sinnvoll ist, dann muss man davon abweichen.Das halte ich fur fast schon groben Unfug. Es ist überhaupt nichts gewonnen, 3 Argumente durch eine Klasse mit 3 Attributen zu ersetzen. Außer es für die Programmiererin komplizierter zu machen.
Im Sinne von "Shu Ha Ri" ist es aber hin und wieder hilfreich, als Anfänger Regeln/Konventionen erst mal zu verstehen, bevor man sie bricht.
@_blackjack_
Ich sehe den Zugriffsschutz allerdings auch z.B. als Kommunikationsmöglichkeit zwischen z.B. Framework- und Produkt-Entwickler.Dazu reicht es wenn man einfach nicht auf Sachen zugreift, die zu Interna gehören. Die kann man einfach mit einem Unterstrich als solche kenntlich machen.
Offensichtlich kann man das aber auch per Konvention sicherstellen. Leider habe ich diesbezüglich noch keine persönlichen Erfahrungen machen können. Aber nun, das kann ja noch werden, denke ich.
Da gebe ich Dir vollkommen Recht. Triviale Getter/Setter machen aus dem bereits oben genannten Grunde keine Sinn.Und in vielen einfachen Einstiegsbeispielen gibt es dann in Sprachen wie Java triviale Getter und Setter, womit die Attribute de fakto doch wieder öffentlich sind. Das ist dann keine Kapselung beziehungsweise ist das nichts anderes als in Python direkt auf Attribute zuzugreifen. Die Argumentation bei Java ist dann, dass man die Interna beim Zugriff jederzeit ändern kann, was beim direkten Zugriff nicht geht.
Was ich aber durchaus sinnvoll finde, ist z. B. nur eine lesenden Beschränkung (sprich "nur" Getter) zu ermöglichen (je nach Semantik natürlich)
Das muss man aber als bewußte Design-Entscheidung treffen. Eine "Voratshaltung" von Code ist selbstredend fast nie sinnvoll.
Ich kenne jetzt natürlich nicht den konkreten Fall. Aber es ist natürlich durchaus vorstellbar, dass in einigen Fällen der Unterschied im Design mit RECORD oder CLASS gering ist.Datenkapselung habe ich das erste mal mit Pascal beigebracht bekommen, und zwar mit klassischem Pascal mit RECORD und Units, ohne OBJECT oder CLASS,.
”OOP” habe ich auch das erste mal mit Pascal gezeigt bekommen — und nicht verstanden warum das jetzt besser sein sollte, weil mir in dem Zusammenhang nichts gezeigt wurde, was das wirklich besser gemacht hat, als die prozedurale Lösung, die wir davor für das gestellte Problem entwickelt hatten.
RECORDS ziehe ich dann eine CLASS vor, wenn ich absehbar keine Polymorphie brauche und wenn sich dadurch z.B. Performenceverbesserungen ergeben.
RECORDS sind auch etwas einfacher in der Handhabung im Hinblick auf Speichermanagment (zumindest in Delphi)
Ja das gefällt mir an Python auch sehr gut. Also dass da, wo es eben Sinn macht auch der Funktionale Part zum Tragen kommt. (Oder habe ich da was missverstanden?)In Python ist da wahrscheinlich `__str__()` eines der einfachsten Beispiele, weil selbst bei POD-Klassen oft eine eigene Zeichenkettendarstellung Sinn macht, und `print()` eine sehr einfache und zu dem Zeitpunkt sicher bekannte Funktion ist, die das dann nutzen kann, dass man eine für den Benutzer sinnvolle Darstellung als Zeichenkette ausgeben kann, in dem man ganz einfach das Objekt an `print()` übergibt.
Ich bin auch kein OOP-Hardliner im Sinne von "nur OOP ist das einzig wahre".
Imho konnte OOP in vielen Aspekten seine Versprechen nicht wirklich einhalten. Aber das dürfte eine andere Diskussion sein #
Ich weiß nicht, ob ich mich trauen sollte, die hier aufzumachen.
ja, das ist korrekt. Es gibt aber eben auch Fälle, wo endlos lange Parameterlisten, die vielleicht noch Gott-weiß-wohin "durchgereicht" werden, zu unwartbarem Code führen.Die Argumentanzahl generell so drastisch zu reduzieren wie das von Martin vorgeschlagen wird, verschiebt das Problem IMHO auch nur zu immer mehr und immer verschachtelteren Argument-Objekten.
Da ist man dann mit einer Klasse (oder einem Record) durchaus "besser" dran.
Letzlich hängt es auch davon ab, wie stabil eine Parameterliste potentiell ist. Bei dem Beispiel mit dem Kreis kann man natürlich davon ausgehen, dass das perspektivisch stabil bleibt.
Hast Du eine Empfehlung, was man da so mal alternativ lesen könnte?Mit Büchern zu statisch typisierten Sprachen wäre ich bei Python (und anderen dynamischen Sprachen) vorsichtig.
ich hab nochmal nachgedacht.
Ggf. braucht man "private" in Python auch gar nicht (so oft), weil es andere/bessere Möglichkeiten gibt, die ich noch nicht kenne.
Wäre es aus Eurer Sicht ratsam, sich z.B. mal die Architektur einer Lib (z.B. Pandas) näher anzusehen, um da mehr zu verstehen?
Ggf. braucht man "private" in Python auch gar nicht (so oft), weil es andere/bessere Möglichkeiten gibt, die ich noch nicht kenne.
Wäre es aus Eurer Sicht ratsam, sich z.B. mal die Architektur einer Lib (z.B. Pandas) näher anzusehen, um da mehr zu verstehen?
- __blackjack__
- User
- Beiträge: 13100
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Buchfink: Ich habe darüber noch mal nachgedacht, und ein Beispiel gesucht für etwas in BASIC, weil der OP ja schon mal von BASIC und GOTO sprach. Beim Advent of Code 2015, Tag 18 war die Aufgabe eine quadratisches Gitter aus Lichtern nach bestimmten Regeln zu animieren (Conway lässt grüssen) und im zweiten Teil gibt es zwei verschiedene Arten von Lichtern — neben den normalen welche die ständig an sind, und die sich auch nicht ausschalten lassen. Die Aufgabe in Kurzform:
Teil 1:
Lichterquadrat bei dem die Anfangskonfiguration als Puzzle-Eingabe gegeben ist, das eine gegebene Anzahl von Schritten nach folgenden Regeln animiert werden soll:
Nachbarn sind die 8 Felder direkt um ein Licht herum. Bei Lichtern am Rand zählen nicht vorhandene Nachbarn nachvollziebarerweise als ausgeschaltet.
Am Ende der vorgegebenen Anzahl von Schritten soll die Gesamthelligkeit berechnet werden, was laut Aufgabe der Anzahl der dann eingeschalteten Lichter entspricht.
Teil 2:
Das selbe wie in Teil 1, nur das jetzt die vier Lichter in den Ecken immer eingeschaltet sind, und sich nicht ausschalten lassen.
In der Aufgabe ist ein kleines 6×6 Beispiel zur Verdeutlichung und zum testen der eigenen Lösung geeignet. "#" ist ein eingeschaltetes Licht und "." ein ausgeschaltetes. Testlauf mit Debugausgaben vom Programm (der erste ”Initial state” entspricht dem Inhalt der Eingabedatei):
Das Programm hat einen abstrakten Typ `TBaseLight` für ein einzelnes Licht mit Methoden zum Umwandeln in eine Zeichenkette ("#" oder ".") einem abstrakten Property um den Zustand zu setzen und abzufragen, einem Property um die Helligkeit abzufragen (0 oder 1) und eine Methode um den Zustand über eine Zeichenkette ("#" oder ".") zu setzen.
Davon abgeleitet sind dann `TLight` und `TStuckLight` die jeweils das Zustandsproperty entsprechend implementieren. Also ein einfaches Beispiel für Polymorphie über Vererbung/Untertypen.
Bei der Implementierung ist mir noch ein Vorteil aufgefallen, der einfacher als Polymorphie ist: Klassen/Typen/Objekte sind Namensräume. Man muss nicht mehr so stark aufpassen eindeutige Prozedur oder Funktionsnamen zu wählen, weil `TBaseLight.brightness` eine andere Funktion als `TLightGrid.brightness` ist, man sich beim Aufruf aber auf `brightness` für beide beschränken kann, weil der Compiler aus dem Aufrufkontext weiss was gemeint ist. Wobei das natürlich streng genommen nicht auf OOP beschränkt ist, denn das könnte man auch über das Überladen von Funktionen/Prozeduren via Signatur erreichen. FreeBASIC könnte das sogar.
In FreeBASIC ist auch die folgende Lösung der Aufgabe geschrieben. Das ist als freier Compiler für QBasic-Quelltexte gestartet, aber mittlerweise um einige Features erweitert. Unter anderem Typen als ”Klassen” mit Properties und Methoden (und Zugriffsschutz).
Konkreter Fall beim Erstkontakt mit ”OOP” war bei mir Informatikunterricht mit Turbo Pascal. Der Lehrplan sah die Entwicklung einer Adressverwaltung mit Units und RECORDs vor. Und das haben wir auch so durchgezogen. Und ganz am Ende hat uns der Lehrer dann die tollen neuen OOP-Konstrukte in Turbo Pascal 5.x gezeigt, in dem er das Program ”objectorientiert” umgeschrieben hat. Also letztlich aus den RECORDs halt Objekte gemacht, was syntaktisch halt ein kleines bisschen anders aussah, aber ich hatte so überhaupt nicht verstanden warum der so aus dem Häusschen war, denn so hat man natürlich nicht wirklich einen Vorteil gesehen, denn letztendlich war es ja das gleiche Programm.
Was mir zu dem Zeitpunkt natürlich auch noch nicht klar war, war das OOP ein Paradigma ist und keine Spracheigenschaft. Letztlich war die prozedurale Version von dem Programm schon sehr nah an der Idee von OOP dran. Was wohl auch ein Grund war, warum ich den Quantensprung in der OOP-Variante nicht erkennen konnte, denn der war halt wirklich sehr klein.
Klick gemacht hat es dann erst bei Java. Das ist ja die Holzhammermethode weil da *alles* in Klassen gepresst werden muss.
Hm, ob bei `__str__()` und `print()` der funktionale Part zum Tragen kommt ist eine gute Frage. Letztlich ist Python “durch und durch“ objektorientiert, denn jeder Typ ist eine Klasse und jeder Wert ist ein Objekt. Das schliesst ja Funktionen mit ein. Auch das sind Objekte, die halt *eine* sehr wichtige Methode haben: `__call__()`. Denn ``something(42)`` steht ja letztlich die Kurzform für ``something.__call__(42)``. Und ob `something` nun für ein Objekt vom Typ `function` steht, oder irgendwas anderes das `__call__()` implementiert, ist ja letztlich egal. Wenn es ``def`` für Funktionen nicht gäbe, sondern nur für Methoden, würde man halt mehr Boilerplate schreiben:
Ist der erste Code jetzt irgendwie objektorientierter, nur weil man da den Boilerplate mit der Klasse schreiben muss? Und wird es plötzlich funktional, wenn man den Boilerplate weglassen kann?
Die Funktionen die in der Regel einer ”magischen“ Methode entsprechen wie `str()`, `repr()`, `len()`, und so weiter, kann man auch als Operatoren auffassen. Das war mal die Antwort von Guido warum `len()` eine Funktion ist. Er sah das eher als Operator, den man überladen kann, und der einen Namen hat, statt dafür irgendein kryptisches Zeichen als extra Syntax zu definieren.
Teil 1:
Lichterquadrat bei dem die Anfangskonfiguration als Puzzle-Eingabe gegeben ist, das eine gegebene Anzahl von Schritten nach folgenden Regeln animiert werden soll:
- Ein Licht das an ist, bleibt an, falls 2 oder 3 Nachbarlichter an sind, sonst geht es aus.
- Ein Licht das aus ist, geht an, falls genau 3 Nachbarlichter an sind, sonst bleibt es aus.
Nachbarn sind die 8 Felder direkt um ein Licht herum. Bei Lichtern am Rand zählen nicht vorhandene Nachbarn nachvollziebarerweise als ausgeschaltet.
Am Ende der vorgegebenen Anzahl von Schritten soll die Gesamthelligkeit berechnet werden, was laut Aufgabe der Anzahl der dann eingeschalteten Lichter entspricht.
Teil 2:
Das selbe wie in Teil 1, nur das jetzt die vier Lichter in den Ecken immer eingeschaltet sind, und sich nicht ausschalten lassen.
In der Aufgabe ist ein kleines 6×6 Beispiel zur Verdeutlichung und zum testen der eigenen Lösung geeignet. "#" ist ein eingeschaltetes Licht und "." ein ausgeschaltetes. Testlauf mit Debugausgaben vom Programm (der erste ”Initial state” entspricht dem Inhalt der Eingabedatei):
Code: Alles auswählen
$ ./day18_fb -h
Usage: ./day18_fb -h | [-s steps] [-d] [filename]
-h print help and exit
-s steps set number of steps (default 100)
-d print debug output
The default filename is input.txt.
$ ./day18_fb -s 5 -d test_input.txt
Initial state:
.#.#.#
...##.
#....#
..#...
#.#..#
####..
[Brightness: 15]
After 1 step:
..##..
..##.#
...##.
......
#.....
#.##..
[Brightness: 11]
After 2 steps:
..###.
......
..###.
......
.#....
.#....
[Brightness: 8]
After 3 steps:
...#..
......
...#..
..##..
......
......
[Brightness: 4]
After 4 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]
After 5 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]
Brightness after 5 steps without stuck corners: 4
Initial state:
##.#.#
...##.
#....#
..#...
#.#..#
####.#
[Brightness: 17]
After 1 step:
#.##.#
####.#
...##.
......
#...#.
#.####
[Brightness: 18]
After 2 steps:
#..#.#
#....#
.#.##.
...##.
.#..##
##.###
[Brightness: 18]
After 3 steps:
#...##
####.#
..##.#
......
##....
####.#
[Brightness: 18]
After 4 steps:
#.####
#....#
...#..
.##...
#.....
#.#..#
[Brightness: 14]
After 5 steps:
##.###
.##..#
.##...
.##...
#.#...
##...#
[Brightness: 17]
Brightness after 5 steps with stuck corners: 17
Davon abgeleitet sind dann `TLight` und `TStuckLight` die jeweils das Zustandsproperty entsprechend implementieren. Also ein einfaches Beispiel für Polymorphie über Vererbung/Untertypen.
Bei der Implementierung ist mir noch ein Vorteil aufgefallen, der einfacher als Polymorphie ist: Klassen/Typen/Objekte sind Namensräume. Man muss nicht mehr so stark aufpassen eindeutige Prozedur oder Funktionsnamen zu wählen, weil `TBaseLight.brightness` eine andere Funktion als `TLightGrid.brightness` ist, man sich beim Aufruf aber auf `brightness` für beide beschränken kann, weil der Compiler aus dem Aufrufkontext weiss was gemeint ist. Wobei das natürlich streng genommen nicht auf OOP beschränkt ist, denn das könnte man auch über das Überladen von Funktionen/Prozeduren via Signatur erreichen. FreeBASIC könnte das sogar.
In FreeBASIC ist auch die folgende Lösung der Aufgabe geschrieben. Das ist als freier Compiler für QBasic-Quelltexte gestartet, aber mittlerweise um einige Features erweitert. Unter anderem Typen als ”Klassen” mit Properties und Methoden (und Zugriffsschutz).
Code: Alles auswählen
'
' Advent of Code 2015 - day 18
'
' Basic light type with boolean state and readonly brightness properties. The
' state can be set with a string and the object can be cast to a suitable
' string.
Type TBaseLight Extends Object
Const ON = "#"
Const OFF = "."
Declare Const Operator Cast() As String
Declare Const Abstract Property state As Boolean
Declare Abstract Property state(value As Boolean)
Declare Const Property brightness As Integer
' Set state from given string. Returns TRUE if the string is a valid state
' representation, FALSE otherwise.
Declare Function SetFromString(value As Const String) As Boolean
End Type
Const Operator TBaseLight.Cast() As String
Return IIf(state, ON, OFF)
End Operator
Const Property TBaseLight.brightness As Integer
Return IIf(state, 1, 0)
End Property
Function TBaseLight.SetFromString(value As Const String) As Boolean
Dim ok As Boolean
ok = TRUE
Select Case value
Case ON
state = TRUE
Case OFF
state = FALSE
Case Else
ok = FALSE
End Select
Return ok
End Function
' Normal light.
Type TLight Extends TBaseLight
Declare Const Property state As Boolean Override
Declare Property state(value As Boolean) Override
Private:
_state As Boolean
End Type
Const Property TLight.state As Boolean
Return _state
End Property
Property TLight.state(value As Boolean)
_state = value
End Property
' A stuck light. Reading the state is always TRUE, writing it has no effect.
Type TStuckLight Extends TBaseLight
Declare Const Property state As Boolean Override
Declare Property state(value As Boolean) Override
End Type
Const Property TStuckLight.state As Boolean
Return TRUE
End Property
Property TStuckLight.state(value As Boolean)
' Setting the state has no effect on a stuck light.
End Property
' A square grid of lights.
'
' The file format is one line per row with "." for a light in off state and "#"
' for a light in on state. Such a file can be loaded with the `Load` method.
'
Type TLightGrid
Declare Destructor
' Cast the this into a string of the format that `Load` expects in a file.
Declare Const Operator Cast() As String
' Load the state from given `filename`. If `hasStuckCorners` is TRUE then the
' lights in the four corners are always on, regardless of the value in the
' file.
Declare Sub Load(filename As Const String, hasStuckCorners As Boolean = FALSE)
' Advance the grid state one step.
'
' The rules are:
'
' * A light that is on, stays on if 2 or 3 neighbours are on,
' otherwise it goes off.
' * A light that is off, goes on if exactly 3 neighbours are on,
' otherwise it stays off.
'
' These rules are applied simultaneously to all lights.
Declare Sub DoStep
' The overall brightness of the light grid.
Declare Const Property brightness As Integer
Private:
size As Integer
lights(Any, Any) As TBaseLight Ptr
' Free the array/memory of the lights. Pulled into an own method because the
' destructor and `Load` need this.
Declare Sub Reset
' Get the light state at given coordinates. If coordinates are outside the
' grid, return FALSE.
Declare Const Function GetStateAt(i As Integer, j As Integer) As Boolean
' Test wether the given coordinates are a corner of the grid.
Declare Const Function IsCorner(i As Integer, j As Integer) As Boolean
' Count the neighbouring lights that are on. This considers all eight
' neighbours around the given coordinates. Off grid positions are
' considered being off.
Declare Const Function CountLitNeightbours(i As Integer, j As Integer) As Integer
End Type
Sub TLightGrid.Reset
Dim i As Integer, j As Integer
If size > 0 Then
For i = 1 To size
For j = 1 To size: Delete lights(i, j): Next
Next
End If
size = 0: Erase lights
End Sub
Destructor TLightGrid
Reset
End Destructor
Const Operator TLightGrid.Cast() As String
Dim result As String, i As Integer, j As Integer, k As Integer
' Reserve space for a `size` by `size` character grid with a line ending
' character at the end of each row.
result = Space(size * (size + 1))
k = 1
For i = 1 To size
For j = 1 To size
Mid(result, k) = *lights(i, j)
k = k + 1
Next
Mid(result, k) = Chr(10)
k = k + 1
Next
Return result
End Operator
Const Function TLightGrid.GetStateAt(i As Integer, j As Integer) As Boolean
If 1 <= i And i <= size And 1 <= j And j <= size Then
Return lights(i, j)->state
Else
Return FALSE
End If
End Function
Const Function TLightGrid.IsCorner(i As Integer, j As Integer) As Boolean
Return i = 1 And j = 1 _
Or i = 1 And j = size _
Or i = size And j = 1 _
Or i = size And j = size
End Function
Sub TLightGrid.Load(filename As Const String, hasStuckCorners As Boolean)
Dim f As Long, row As String, i As Integer, j As Integer, c As String
Dim light As TBaseLight Ptr
If size > 0 Then Reset
f = FreeFile
Open filename For Input As #f
Line Input #f, row
size = Len(row)
ReDim lights(1 To size, 1 To size)
For i = 1 To size
For j = 1 To size
If hasStuckCorners And IsCorner(i, j) Then
light = New TStuckLight
Else
light = New TLight
End If
c = Mid(row, j, 1)
If Not light->SetFromString(c) Then
Print row
Print Spc(j - 1); "^"
Print "Unexpected ";
If c = "" Then
Print "end of file";
Else
Print "character '"; c; "'";
End If
Print " at line"; i
End 1
End If
lights(i, j) = light
Next
Line Input #f, row
Next
Close #f
End Sub
Const Function TLightGrid.CountLitNeightbours(i As Integer, j As Integer) As Integer
Dim result As Integer, di As Integer, dj As Integer
result = 0
If size > 0 Then
For di = -1 To 1
For dj = -1 To 1
If Not (di = 0 And dj = 0) Then
If GetStateAt(i + di, j + dj) Then result = result + 1
End If
Next
Next
End If
Return result
End Function
Sub TLightGrid.DoStep
Dim newStates(1 To size, 1 To size) As Boolean
Dim i As Integer, j As Integer, count As Integer
If size > 0 Then
' Calculate new states.
For i = 1 To size
For j = 1 To size
count = CountLitNeightbours(i, j)
If GetStateAt(i, j) Then
newStates(i, j) = count = 2 Or count = 3
Else
newStates(i, j) = count = 3
End If
Next
Next
' Set new states.
For i = 1 To size
For j = 1 To size
lights(i, j)->state = newStates(i, j)
Next
Next
End If
End Sub
Const Property TLightGrid.brightness As Integer
Dim i As Integer, j As Integer, result As Integer
result = 0
If size > 0 Then
For i = 1 To size
For j = 1 To size
result = result + lights(i, j)->brightness
Next
Next
End If
Return result
End Property
' Print the step number, the grid state, and the grid's brightness for debugging
' purposes.
Sub PrintLights(lights As Const TLightGrid, stepNumber As Integer = 0)
Print
If stepNumber = 0 Then
Print "Initial state:"
Else
Print "After"; stepNumber; " step"; IIf(stepNumber > 1, "s", ""); ":"
End If
Print lights;
Print "[Brightness:"; lights.brightness; "]"
End Sub
'
' The main program.
'
Const DEFAULT_STEP_COUNT = 100, DEFAULT_FILENAME = "input.txt"
Dim filename As String, stepCount As Integer, debug As Boolean
filename = DEFAULT_FILENAME
stepCount = DEFAULT_STEP_COUNT
debug = FALSE
Scope
Dim i As Integer
i = 1
Do While Command(i) <> ""
Select Case Command(i)
Case "-h"
Print "Usage: "; Command(0); " -h | [-s steps] [-d] [filename]"
Print
Print " -h print help and exit"
Print "-s steps set number of steps (default"; DEFAULT_STEP_COUNT; ")"
Print " -d print debug output"
Print
Print "The default filename is "; DEFAULT_FILENAME; "."
End 0
Case "-s"
i = i + 1
stepCount = Val(Command(i))
Case "-d"
debug = TRUE
Case Else
If Left(Command(i), 1) = "-" Then
Print "Unknown option: "; Command(i); "."
Print "Try -h for help."
End 1
End If
filename = Command(i)
End Select
i = i + 1
Loop
End Scope
Scope
Dim lights As TLightGrid, i As Integer, hasStuckCorners As Integer
For hasStuckCorners = 0 To 1
lights.Load filename, hasStuckCorners
If debug Then PrintLights lights
If stepCount > 0 Then
For i = 1 To stepCount
lights.DoStep
If debug Then PrintLights lights, i
Next
End If
Print "Brightness after"; stepCount; " steps "; _
IIf(hasStuckCorners, "with", "without"); " stuck corners:"; _
lights.brightness
Next
End Scope
Was mir zu dem Zeitpunkt natürlich auch noch nicht klar war, war das OOP ein Paradigma ist und keine Spracheigenschaft. Letztlich war die prozedurale Version von dem Programm schon sehr nah an der Idee von OOP dran. Was wohl auch ein Grund war, warum ich den Quantensprung in der OOP-Variante nicht erkennen konnte, denn der war halt wirklich sehr klein.
Klick gemacht hat es dann erst bei Java. Das ist ja die Holzhammermethode weil da *alles* in Klassen gepresst werden muss.
Hm, ob bei `__str__()` und `print()` der funktionale Part zum Tragen kommt ist eine gute Frage. Letztlich ist Python “durch und durch“ objektorientiert, denn jeder Typ ist eine Klasse und jeder Wert ist ein Objekt. Das schliesst ja Funktionen mit ein. Auch das sind Objekte, die halt *eine* sehr wichtige Methode haben: `__call__()`. Denn ``something(42)`` steht ja letztlich die Kurzform für ``something.__call__(42)``. Und ob `something` nun für ein Objekt vom Typ `function` steht, oder irgendwas anderes das `__call__()` implementiert, ist ja letztlich egal. Wenn es ``def`` für Funktionen nicht gäbe, sondern nur für Methoden, würde man halt mehr Boilerplate schreiben:
Code: Alles auswählen
class Something:
def __call__(self, argument):
...
something = Something()
# statt einfach nur
def something(argument):
...
Die Funktionen die in der Regel einer ”magischen“ Methode entsprechen wie `str()`, `repr()`, `len()`, und so weiter, kann man auch als Operatoren auffassen. Das war mal die Antwort von Guido warum `len()` eine Funktion ist. Er sah das eher als Operator, den man überladen kann, und der einen Namen hat, statt dafür irgendein kryptisches Zeichen als extra Syntax zu definieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman