Lotus Notes / Domino Sonstiges > Projekt Bereich

Gang Of Four (GoF) Design Patterns für LotusScript on OO nachprogrammieren?

<< < (9/10) > >>

Marinero Atlántico:
Mit der großen roten 1 im Bild gekennzeichnet fällt auf, dass Duck:New 3 mal aufgerufen wird. RedHeadDuck.New und MallardDuck.New() werden aber nur 1 mal aufgerufen. Komisch. Komisch. 
Wer sich das nicht direkt erklären kann, schaut sich den Ablauf des codes am besten direkt mal im Debugger an.
Direkt bei den ersten steps!
Und was passiert dort im Debugger?
Richtig. Wir rufen.

--- Code: ---Set myRHDuck = New RedHeadDuck()

--- Ende Code ---
auf und es wird erst der Konstruktor von Duck aufgerufen und dann erst der Konstruktor von RedHeadDuck.
Das gleiche Phänomen ist bei dem Aufruf von MallardDuck zu beobachten.
Deshalb gibt es im Logger 3 Einträge von Duck.New pro Durchlauf. Schliesslich wird er – wie zu erwarten – noch mal bei new Duck() aufgerufen (letztes Code-Segment im Agenten.Initialize).

Die Methode new ist, wie bereits erwähnt, eine spezielle Methode. Ein Konstruktor, um genau zu sein. Konstruktoren von Klassen in einer Vererbungshierarchie werden also scheinbar bei der Objektintiierung in einer Art Hierarchie aufgerufen.
Und zwar als erstes der Konstruktor der oberste Hierarchie und zum Schluss der unterste in der Vererbungshierarchie (der initiierte Typ).
Konstruktoren dienen der Initiierung von Instanzvariablen. MallardDuck is-a Duck und deshalb muss auch der Konstruktor von Duck aufgerufen werden (oberste in der Hierarchie) und dann RedHeadDuck (eigentlicher Typ der erzeugt wird und erbt direkt von Duck).
Würde Ducks eine Oberklasse Birds haben und RedheadDuck von Duck erben, würde zunächst der Konstruktor von Birds aufgerufen, dann Duck und dann RedHeadDuck.   

Eine Schwäche von LotusScript OO gegenüber anderen OO-Sprachen ist, dass die Konstruktoren von Klassen in einer Vererbungshierarchie die gleichen Parameter übergeben werden m.ü.s.s.e.n. Bei dem Entenbeispiel fällt das nicht weiter auf, weil alle new Methoden (Konstruktoren) keine Parameter haben.
Sobald aber z.B. das new von MallardDuck ein Parameter übergeben wird (z.B.

--- Code: ---New (name as String)

--- Ende Code ---
muss auch Duck einen Konstruktor mit einem String-Parameter haben.
Den Konstruktor auf keine Parameter belassen würde dazu führen, dass die Klasse nicht kompiliert mit einer entsprechenden aussagekräftigen Fehlermeldung im Designer.

In Java ist man da ein bischen freier.

Als erste Zeile im Konstruktor kann man das Schlüsselwort super benutzen, um dem unmittelbar aufgerufenen Parent-Konstruktor andere Parameter zu übergeben (In Java heissen Konstruktoren nicht new sondern haben immer den Namen der Klasse. Der Konstruktor von Duck heisst also in Java nicht new sondern Duck. Das ist aber nur syntaktischer Zucker, heisst: unwichtig:


--- Code: ---class Duck {
   public Duck () {}
}


class MallardDuck extends Duck {
  public MallardDuck(String name) {
    super();
 }
}

--- Ende Code ---
Dieses super muss aber in der ersten Zeile des Konstruktors der Kind-Klasse stehen.
Sieht aus wie ein normaler Aufruf, muss aber an erster Stelle stehen. Das kann Anfänger verwirren. 

C#.NET ist vielleicht in seiner erst mal merkwürdig aussehenden Syntax ein bischen deutlicher: (Vorsicht ungetester Code)

--- Code: ---class Duck {
 Duck () {}
}

public class MallardDuck : Duck { 
// der Doppelpunkt steht für Vererbung wie „extends“ in Java oder „as“ in LotusScript
 public MallardDuck (String name): base() {

 }
}

--- Ende Code ---
: base() steht für die Parameter-Übergabe an den  Parent-Constructor.

Wir sehen, dass die is-a Beziehung (Vererbung) eine sehr enge Kopplung von 2 Klassen verursacht. Tatsächlich ist RedHeadDuck eine Duck, weil eben der Konstruktor von Duck aufgerufen wird, wenn man RedHeadDuck erzeugt.

Was ist eigentlich, wenn man keine Lust hat oder vergisst einen Konstruktor zu schreiben?
Dann erzeugt der Compiler in LotusScript, Java und .NET einen leeren Default-Konstruktor ohne Parameter. Ihr könnt spasseshalber den new() Konstruktor von MallardDuck ausremen (mit %rem %end rem) und sehen was passiert, wenn ihr dann den Agenten laufen lässt. Dort ist auf jeden Fall ein Konstruktor. Dieser Compiler-generierte Konstruktor sieht so aus:

--- Code: ---Public sub new

End sub

--- Ende Code ---
Er steht nur nicht im code, wohl aber in der kompilierten Klasse.
Schwerwiegendere Auswirgungen hat das ausremen des Konstruktors von Duck. Die Klassen kompilieren zwar, dafür terminiert aber eine Runtime Exception „Object Variable Not Set“ den Programmablauf. Das ist so, weil der Konstruktor von Duck die wichtige Aufgabe hat, die Variable myLogger zu initialisieren. Remt man den vorhandenen Konstruktor aus, wo diese Variable inititalisiert wird und benutzt statt dessen den Default-Konstruktor, wo diese Variable nicht initialisiert wird, weil da keine Anweisungen wie die wichtige

--- Code: --- Set myLogger = New Logger()

--- Ende Code ---
fehlt, ist diese Instanzvariable im weiteren Programmverlauf nicht gesetzt und es passiert in der Zeile

--- Code: ---Call myLogger.setIdentifier("DecoyDucksDontFly_Start::RedHeadDuck")

--- Ende Code ---
genau diese „Object Variable not Exception“.


Kommen wir zum nächsten Punkt. Das heisst Moment.
Warum stehen eigentlich in der Log-Datenbank die beiden Duck:new(), die über den Aufruf new MallardDuck() bzw. new RedHeadDuck aufgerufen worden sind unter der Sektion:
DecoyDucksDontFly_Start::Duck
und nicht unter DecoyDucksDontFly_Start::MallardDuck bzw. DecoyDucksDontFly_Start::RedHeadDuck?
Das ist nicht gut.
Nochmal. Was passiert genau bei dem Aufruf von new RedHeadDuck?
1.   RedHeadDuck.new() wird aufgerufen.
2.   Notes sieht, dass diese Klasse eine Superklasse Duck hat.
3.   Notes leitet den Aufruf auf Duck.new() weiter
4.   Im new() von Duck wird ein neues Logger-Objekt wird initialisiert und der Instanzvariable myLogger der Klasse Duck wird dieses Logger Objekt zugewiesen Set myLogger = New Logger()
5.   Die Methode .setIdentifier der Klasse Logger wird mit dem Parameter "DecoyDucksDontFly_Start::Duck" aufgerufen.
6.   In der Methode setIdentifier von Logger wird die Eigenschaft „Programname“ unseres NotesLog-Objekts geändert. Diese manipuliert die „Sektion“ in der Ansicht der Log-Datenbank (nähere Details zu Programname s. Noteshilfe)
7.   Es wird die Methode log("Info:Duck::New terminates") von Logger aufgerufen. Die Sektion steht auf "DecoyDucksDontFly_Start::Duck".
8.   Der Konstruktor new() der Superklasse Duck ist abgearbeitet und nun wird der Konstruktor der eigentlichen Klasse abgearbeitet (RedHeadDuck.new().
9.   In dem Konstruktor new() von RedHeadDuck wird wieder die Methode setIdentifier der Klasse Logger aufgerufen. Nun wird dieser Methode aber ein anderer Parameter übergeben. ("DecoyDucksDontFly_Start::RedHeadDuck").
10.   Wieder wird die Eigenschaft Programname unseres NotesLog-Objekts geändert in myLogger.setIdentifier. Der übergebene Parameter heisst aber nun "DecoyDucksDontFly_Start::RedHeadDuck" und nicht wie im Konstruktor von Duck "DecoyDucksDontFly_Start::Duck".
11.   Die Methode log von myLogger wird mit dem Parameter ("Info:RedHeadDuck.New called" aufgerufen. Das steht nun aber unter der neuen Sektion (Auswirkung von Punkt 10).

Mir gefällt das Verhalten nicht, dass die Logging-Section sich während eines new Aufrufs aus initialize (dem Client code) ändert. Welcher poofy wanker hat das programmiert? Ich weiss, dass auch ich einmal angefangen habe mit Object Orientation, aber muss man so code veröffentlichen? Ich werde das bald so umschreiben, dass das Logger Objekt im Agenten erzeugt wird und den Konstruktoren der Klasse als Parameter übergeben wird.
Oder irgendwie anders. Jedenfalls ist das eine ganz gute Diskussionsgrundlage für OO-Designpraktiken. 
Das initialisieren von myLogger im Konstruktoren in der Basis-Klasse war keine gute Idee!
To strong a coupling. Leider hat Igor z.Zt. wegen IBM.486 Vorbereitung meinen Larman, aber ich bin mir ziemlich sicher, dass sich dort klarere theoretische Fundierungen finden, warum das jetzt dazu führte, dass die Klassen redesigned werden müssen.

--- Code: ---
--- Ende Code ---

Marinero Atlántico:
Themenwechsel: Ein bischen seltsam mag vielleicht auch erscheinen, warum in der Klasse RedHeadDuck überhaupt Methoden der Eigenschaft myLogger aufgerufen werden können. Schliesslich hat diese Klasse überhaupt keine Eigenschaft myLogger.  Nur die SuperKlasse hat eine solche Eigenschaft:

--- Code: ---Private myLogger As Logger

--- Ende Code ---
Offenbar kann aber die Kind-Klasse RedHeadDuck diese Eigenschaft der Elternklasse Duck mitbenutzen. Genau das ist ein Aspekt der Vererbung. Kindklassen tragen die Eigenschaften und Methoden ihrer Elternklasse in sich. Oft zumindest.
Mich hat das an LotusScript erst ein bischen verwirrt. In Java werden Eigenschaften mit dem Zugriffs-Modifier private nicht an child-Klassen vererbt.

In Java sieht es so aus:

--- Code: ---public class Parent {
Parent() {
System.out.println("****In Parent Konstruktor *******");
System.out.println(publicMember);
System.out.println(protectedMember);
System.out.println(packagePrivateMember);
System.out.println(privMember); // hier kann im Gegensatz von Child auf privMember zugegriffen werden!!!



}
// nur innerhalb der Klasse sichtbar. Von Child aus kein Zugriff.
private String privMember = "privMember ist da.";
// vor der nächsten Eigenschaft oder Membervariable steht kein Access-Modifier
// aber auch der leere Access Modifier setzt bestimmte Regeln.
// Solche Variablen sind nur innerhalb des Pakets sichtbar.
// wir haben hier oben in dieser Klasse kein package Statement.
// Deshalb landet die Klasse im default package. Ausserhalb von solchen
// mini-Demo Anwendungen schreibe ich keine Klassen ohne package.
String packagePrivateMember = "packagePrivateMember ist da.";
// protected: wie packagePrivate (also sichtbar innerhalb des gleichen Pakets) und zusätzlich
// alle Klassen, die von dieser Klasse erben und in einem anderen Paket sind.
protected String protectedMember = "protectedMember ist da.";
// public: Vollzugriff. Aus allen Klassen kann auf diese Instanzvariable zugegriffen werden.
public String publicMember = "publicMember ist da.";
}

public class Child extends Parent {

Child() {
System.out.println("****In Child Konstruktor *******");
System.out.println(publicMember);
System.out.println(protectedMember);
System.out.println(packagePrivateMember);
//System.out.println(privMember);
//Lässt compiler nicht zu.
//Auf private members kann nur in der Klasse selbst zugegriffen werden.
}
public static void main(String[] args) {
new Child();
}
}

--- Ende Code ---

Zwar kennen sowohl Java als auch LS Zugriffs-Modifier, jedoch heisst private in beiden Sprachen offenbar etwas leicht anderes. Das ist völlig normal. Auch bei C++/C# und Java gibt es jeweils leicht andere Zugriffs-Modifier. Die grundsätzliche Idee von private ist aber überall gleich. Es ist der Zugriffs-Modifier mit der höchsten Kappselungsstufe. Also der, der am restriktivsten Zugriffe von aussen einschränkt.
Wenn aber nun private Eigenschaften in Lotus-Script fröhlich von anderen Klassen aufgerufen werden können, vielleicht haben die LotusScript Programmierer einfach vergessen oder keine Zeit mehr gehabt das zu programmieren.
Doch es gibt einen Schutz.
Wenn ich am Ende des codes in Initialize des Agenten die Zeile:

--- Code: ---myShouldNotBeInitializedDuck.myLogger

--- Ende Code ---
Erhalte ich die Meldung: Not a public member MyLogger.
Auf private Member habe ich also aus anderen Modulen keinen Zugriff.
Wenn ich die Doku richtig verstanden habe, dann heisst private in LotusScript:
private für das gleiche Modul. Ich werde das aber noch genauer prüfen.
Wenn die Klassen in derselben Declaration-Section stehen können sie gegenseitig auf private Members zugreifen. Sobald sie in anderen Declaration Sections stehen oder der aufrufende Code im Initialize gibt es keinen Zugriff (ungeprüfte Hypothese, mein Verständnis der doku).

Auf jeden Fall bleibt die Tatsache, dass der code ausserhalb des Moduls, nämlich im Initialize des Agenten etwas anderes sieht als der code innerhalb der Klasse.
Genau dieses Phänomen ist der Kern von Kappselung. Aus OO-Sicht bezeichnet man alle Methdoden und Eigenschaften eines Objekts, auf die im jeweiligen Kontext zugegriffen werden kann als Interface. Dieses Interface aus OO-Sicht hat nur über 2 Ecken etwas mit dem Java-Schlüsselwort „Interface“ zu tun. Insgesamt ist es ein Synonym (je nach Kontext eine unterschiedliche Bedeutung). Auch LotusScript Klassen haben ein Interface. Sogar – wie alle OO-Klassen – direkt mehrere. Nämlich je nach Kontext. Für Aufrufe innerhalb der Klasse oder des Moduls gibt es ein private-Interface (Methoden und Eigenschaften mit public oder private Modifier) und ein public Interface (für Zugriffe von aussen, etwa aus dem Initialize eines Agenten wie im Beispiel). Dieses public Interface besteht nur aus den Methoden und Eigenschaften mit public modifier. Die mit private sind verborgen (hidden).
Dies hat extrem wichtige Auswirkungen, die noch später näher erläutert werden sollen.

Frage: Es gibt aber auch Eigenschaften und Methoden ohne public oder private Zugriffs Modifier. Hat das vielleicht in LotusScript auch eine spezielle Bedeutung wie in Java?
Nein. Hat es nicht. Der Compiler fügt den Eigenschaften ohne Zugriffs-Modifier automatisch ein private hinzu und den Methoden automatisch ein public.

Frage: Warum ist das für beide verschieden? Wäre es nicht einfacher, wenn sowohl Methoden als auch Eigenschaften public wären oder private. Zumindest alle einheitlich?
Eigentlich nicht. Nach normaler OO-Praxis sind Methoden eher, aber nicht immer public und Eigenschaften eher, aber nicht immer private.
Das ist mehr als eine Konvention. Aber das versuche ich später zu erklären. Da muss erst mal ein bischen über public und private Interfaces geredet werden.

Als nächstes wird
-   das Redesign durchgeführt (kann wegen mir auch wer anders machen, ist aber egal)
-   sich mal angeschaut was es mit dieser merkwürdigen delete() Methode auf sich hat, die es zwar in der Klasse gibt und gemäss dem Ergebnis in der Log-DB irgendwie aufgerufen wird. Wer sich den code genau anschaut, sieht das im code diese Aufrufe nicht stattfinden.
-   Der Frage nachgegangen, dass zwar der Konstruktor von RedHeadDuck den parent-Constructor aufruft, die in Duck definierte Methode swim() aus RedHeadDuck aufgerufen wird. Bei der Methode display(), die es sowohl in den Eltern als auch in den Kind Klassen gibt, aber nur die display() Methode des jeweiligen Objekts aufgerufen wird (beim Konstruktor werden ja beide aufgerufen). Alles ein bischen verwirrend, vielleicht.

Semeaphoros:
Axel, Du hast das mit Private schon richtig interpretiert, hat aber nix mit OO zu tun, sondern "übergeordnet" mit LS: die kleinste Scope-Einheit ist gemäss LS-Definition das Modul und nicht die Class oder whatever in einem Modul sonst noch granularer sein könnte .... irgendwo hatte ich das mal sogar ausdrücklich gelesen, weiss nur nicht mehr wo (wahrscheinlich im LS-Programmer Guide)

Mark³:

--- Zitat von: Marinero Atlántico am 11.01.05 - 17:25:58 ---
-   Der Frage nachgegangen, dass zwar der Konstruktor von RedHeadDuck den parent-Constructor aufruft, die in Duck definierte Methode swim() aus RedHeadDuck aufgerufen wird. Bei der Methode display(), die es sowohl in den Eltern als auch in den Kind Klassen gibt, aber nur die display() Methode des jeweiligen Objekts aufgerufen wird (beim Konstruktor werden ja beide aufgerufen). Alles ein bischen verwirrend, vielleicht.


--- Ende Zitat ---

um mir das Gefühl des Mitmachens zu geben: Die display() Methode wird nur vom jeweiligen Objekt aufgerufen, weil sie überladen ist (geht in LS hier, weil alle display() die gleichen Parameter erwarten, nämlich keinen) . Wobei der Sinn der Überladung darin liegt dass man   mit dem gleichen Interface sowohl Eltern-Objekte als auch Kind-Objekte ansprechen kann  ::)    ???

Marinero Atlántico:

--- Zitat von: Semeaphoros am 11.01.05 - 17:41:02 ---Axel, Du hast das mit Private schon richtig interpretiert, hat aber nix mit OO zu tun, sondern "übergeordnet" mit LS: die kleinste Scope-Einheit ist gemäss LS-Definition das Modul und nicht die Class oder whatever in einem Modul sonst noch granularer sein könnte .... irgendwo hatte ich das mal sogar ausdrücklich gelesen, weiss nur nicht mehr wo (wahrscheinlich im LS-Programmer Guide)

--- Ende Zitat ---

So etwas ähnliches schwirrte in meinem Kopf auch rum.
Und ja sicher: Private gibt es auch für Funktionen und Subs.
Ich finds nicht so schlimm. Das echte Problem für "modernes"-GoF OO sind die fehlenden abstrakten Klassen und das fehlende static.

Navigation

[0] Themen-Index

[#] Nächste Seite

[*] Vorherige Sete

Zur normalen Ansicht wechseln