# super- Methode aufrufen



## Generic1 (7. Dez 2010)

Hallo,

was haltet ihr von diesem Konstrukt:


```
public class Basisklasse {
   
   public Object methode() {
      irgendwas
      }
}
```


```
public class SubKlasse extends Basisklasse {
   
  @Override
  public Object methode() {
     if(super.methode() == null)    // das kommt mir da nicht ganz geheuer vor, was sagt ihr dazu
        //mach was
     }
}
```

irgendwie ist mir dieser Code nicht geheuer. Was haltet ihr davon bzw. ich bin mir sicher, dass man das besser lösen kann, aber wie.
lg


----------



## Niki (7. Dez 2010)

ist ein gültiges konstrukt und manchmal benötigt man es halt. wenn du z.b. bein eigenes Document für ein TextFeld brauchst, musst du auch damit arbeiten.


----------



## Generic1 (7. Dez 2010)

Tut aber irgendwie weh, ich bin mir jetzt nicht sicher, ob man das so machen soll, ich hab das noch nie benötigt. Mir erscheint das als ein schlechtes Design!?


----------



## SlaterB (7. Dez 2010)

schön ist das nicht, habe ich glaube ich noch nie benutzt oder irgendwo sinnvoll gesehen,
möglich ist es technisch natürlich, aber meiner Ansicht nach sehr zu vermeiden

(edit: doch eher übertrieben von mir)


----------



## Andi_CH (7. Dez 2010)

Was empfindest du als schlecht? Den Vergleich mit null (also der Zweifel ob die Oberklasse in jedem Fall etwas vernünftiges liefert) oder die Tatsache dass eine Oberklassenmethode aufgerufen wird?

Ersteres kann immer sein.

Wenn ich init Methoden von Konstruktoren trenne mache ich das auch. (Ich finde es ist ein gutes Design die Variablen der Vaterklasse auch in dieser zu initialisieren)

Es ist sicher nicht grundsätzlich zu verdammen aber auch nicht in jedem Fall gut.


----------



## Generic1 (7. Dez 2010)

Was mich stört ist erstens, dass ich aus einer überschriebenen Methode die überschriebene Methode aufrufe -> Ich möchte ja durch das Überschreiben einer Methode ausdrücken, dass ich das Verhalten verändern oder erweitern will, nicht aber, wenn das nicht geht dann mach halt was anderes -> da fällt mir sofort das Strategy Muster ein -> Verschiedene Verhalten kapseln. 

Und die auf "null" fragerei find ich sowieso nur in Ausnamhefällen angebracht. 
Deswegen hab ich an diesem Konstrukt so meine Zweifel!?


----------



## KSG9|sebastian (7. Dez 2010)

Das Verhalten sollst du sowieso nicht ändern.

Design by Contract ist das Stichwort.

Kleines Beispiel


```
class Test{
   /**
    * Dividiert d1 durch d2
    * Wenn d2 0 ist wird -1 zurückgegeben.  
    */
   public double divide(double d1, double d2){
      if(d2 == 0){
          return -1;
      }
      return d1/d2;
   }
}
class TestSub extends Test{
   public double divide(double d1, double d2){
      if(d2 == 0){
          throw new IrgendwasException("zero not allowed");
      }
      return super.divide(d1, d2);
   }
}

class Usecase{
    public void foo(){
       Test cal = MyFactory.getCalculator();
       cal.divide(1, 0);
    }
}
```

TestSub ändert das Verhalten der Methode divide grundlegend: Es wird plötzlich eine Exception geworfen statt -1 zurückgegeben.
Der Aufrufer weiß aber gar nicht mit welcher Implementierung gearbeitet wird. Er verlässt sich darauf das -1 kommt. Irgendjemand dreht nun die Implementierung in der Factory und rums - schon kracht der Code.

Daher ist auch in deinem Beispiel die frage:
Was tut die Methode genau? Es mag schon korrekt sein auf null zu prüfen, aber eben nur unter der Bedigung das der Vertrag zwischen (so gesehen) Serviceuser und Serviceprovider nicht verletzt wird.

Im Prinzip kann ich nix schlechtes an deinem Beispiel erkennen.
Eine super-Methode aufzurufen - warum denn nicht?
Auf null prüfen - auch nicht unbedingt schlimm?

Oftmals wird sowas benötigt....


```
class MyDocument{
  
   public void insertText(String str..){
     // do something..
   }

}

class MyValidationDocument extends MyDocument{
   private String allowedChars = "0123456789";
   
   public void insertText(String str..){
      if(isValid(str)){
         super.insertText(str...);
      }
   } 

}
```


----------



## Andi_CH (7. Dez 2010)

Ich überprüfe eigentlich bei jedem Aufruf den Rückgabewert.

Abgeleitete Klassen *erweitern* das Verhalten der Vaterklasse.

Es gibt z.B. mehr Attribute, also ruft meine init - Methode die init Methode der Vaterklasse auf und initialisiert danach die Attribute der eigenen Klasse (Ich hab sogar schon einzelne Attribute der Vaterklasse an diesem Punkt auf andere Werte geändert.)

Grundsätzlich ist es doch so, dass wenn eine Methode der eigentlich alles richtig macht, kopiere ich ganz sicher nicht den Code, sondern rufe sie auf und mache die zusätzlichen Dinge auch noch. 

Wir mussten vor einiger Zeit ein Bemessungsprogramm so abändern, dass es im UK/US Masssystem funktioniert. Da haben wir das auch so gemacht, dass wir die neuen Klassen von den mks (Meter-kg-Sekunden) -Klassen abgeleitet haben. Die überschriebenen Funktionen haben die Eingabewerte umskaliert, "sich selbst" in der mks-Klasse aufgerufen und das Resultat auch umskaliert. 
Das finde ich nicht ideal. Ich persönlich hätte da eine Interfaceklasse (nicht zu verwechseln mit Java Interfaces, aber ich weiss nicht ob man dem Bridge sagt) davorgesetzt, welche die Daten umskaliert, aber das war ein Architekturentscheid mit dem ich leben konnte.

Aber wie schon gesagt, das ist natürlich immer abzuwägen und sicher auch Geschmackssache ob es auch wirklich ein Vorteil ist.


----------



## SlaterB (7. Dez 2010)

super an sich ist natürlich schon ok und geläufiger, bei init() wird nur Code ausgeführt

meine Kritik bezog sich direkt auf das Beispiel am Anfang, Rückgabewert,
aber ok, denkbar ist das auch..


----------



## tfa (7. Dez 2010)

> Es gibt z.B. mehr Attribute, also ruft meine init - Methode die init Methode der Vaterklasse auf und initialisiert danach die Attribute der eigenen Klasse


Verkettete Konstruktoren eben, das ist das Standardverfahren mit dem super(...)-Aufruf. 



> (Ich hab sogar schon einzelne Attribute der Vaterklasse an diesem Punkt auf andere Werte geändert.)


Wenn man sowas macht, muss man verdammt aufpassen, das Substitutionsprinzip nicht zu verletzten. Ist die Kindklasse dann wirklich noch vom Typ der Mutterklasse oder verhält sie sich anders.


----------



## ThreadPool (7. Dez 2010)

KSG9|sebastian hat gesagt.:


> Das Verhalten sollst du sowieso nicht ändern.
> Design by Contract ist das Stichwort.



In welcher Abhandlung zum DbC (der Begriff ist übrigens geschützt) steht das man Verhalten nicht ändern darf? Dann wären Vererbung mit einhergehender Polymorphie nutzlos. Das was du du in deinem Beispiel demonstrierst ist nicht das ändern des Verhaltens sondern du demonstrierst den Verstoß das man in einer abgeleiteten Klasse die Vorbedingungen höchstens abschwächen jedoch nicht verstärken soll. [1]

[1] Das fordert IMHO das DbC und das Liskovsche-Substitutionsprinzip (sind eng verbunden), ich bin mir aber gerade nicht sicher wer zu erst da war.


----------



## SlaterB (7. Dez 2010)

"du demonstrierst den Verstoß das man in einer abgeleiteten Klasse die Vorbedingungen 
höchstens abschwächen jedoch nicht verstärken soll." == Verhalten ändern?


----------



## ThreadPool (7. Dez 2010)

SlaterB hat gesagt.:


> "du demonstrierst den Verstoß das man in einer abgeleiteten Klasse die Vorbedingungen
> höchstens abschwächen jedoch nicht verstärken soll." == Verhalten ändern?



Dann haben wir andere Vorstellungen von "Verhalten", hätte er in der abgeleiteten Methode multipliziert wäre das für mich geändertes Verhalten (was aber nicht dem "Contract" entspräche den jmd erwartet der eine Methode zum Dividieren aufruft)


----------



## SlaterB (7. Dez 2010)

es kommt ja immerhin eine andere Rückgabe bzw. gar keine Rückgabe zu mindest einer bestimmten Eingabe, 
das ist der Multiplikation, die auch die Rückgabe ändert, ziemlich nahe, 
dass nur eine bzw. relativ wenige statt ziemlich viele Eingabekombinationen betroffen sind, ist kein Argument,

aber egal, nicht direkt das Hauptthema hier


----------



## Andi_CH (7. Dez 2010)

tfa hat gesagt.:


> Verkettete Konstruktoren eben, das ist das Standardverfahren mit dem super(...)-Aufruf.
> 
> Wenn man sowas macht, muss man verdammt aufpassen, das Substitutionsprinzip nicht zu verletzten. Ist die Kindklasse dann wirklich noch vom Typ der Mutterklasse oder verhält sie sich anders.



Die Objekte der Kindklasse starten mit anderen Anfangswerten als es Objekte der Vaterklasse tun. Wieso soll es also von einem anderen Typ sein?

init() ist in dem Fall nichts als eine set Methode die alle Attribute auf Defaultwerte setzt.

Ich bestreite nicht, dass man immer aufpassen sollte was man macht ;-)

init() wird im konkreten Fall auch von den Konstruktoren aufgerufen, was heisst dass der Code von super.init() zwei mal ausgeführt wird.
 -> Dieses init enthält wirklich nur Wertzuweisungen! Keine "new", keine weiteren Aufrufe.


----------



## tfa (7. Dez 2010)

> Die Objekte der Kindklasse starten mit anderen Anfangswerten als es Objekte der Vaterklasse tun. Wieso soll es also von einem anderen Typ sein?


Anderer Typ war von mir falsch ausgedrückt. Ich meine, an die Stelle der Mutterklasse muss immer ein Objekt einer beliebigen Kindklasse treten können, ohne den Vertrag zu brechen. Wenn man jetzt bei der Initialisierung der Kindklasse Attribute der Mutterklasse ändert (etwa weil die Unterklasse Zusicherungen über die vererbten Attribute macht), ist das ein Hinweis auf eine Verletzung des Liskovschen Substitutionsprinzips. 



> init() wird im konkreten Fall auch von den Konstruktoren aufgerufen


Dann ist init hoffentlich private oder final. Andernfalls kann man sich schöne Bugs programmieren.


----------



## Andi_CH (7. Dez 2010)

tfa hat gesagt.:


> Anderer Typ war von mir falsch ausgedrückt. Ich meine, an die Stelle der Mutterklasse muss immer ein Objekt einer beliebigen Kindklasse treten können, ohne den Vertrag zu brechen. Wenn man jetzt bei der Initialisierung der Kindklasse Attribute der Mutterklasse ändert (etwa weil die Unterklasse Zusicherungen über die vererbten Attribute macht), ist das ein Hinweis auf eine Verletzung des Liskovschen Substitutionsprinzips.
> 
> Dann ist init hoffentlich private oder final. Andernfalls kann man sich schöne Bugs programmieren.



Wie bitte? Ähm du willst ja wohl nicht etwa sagen, dass ich in einem Kindobjekt nicht den Wert eines ererbten Attributes ändern darf? Sei das nun direkt weil es protected ist oder über einen setter.

Genau so lese ich aber deinen Einwand. (Liskov? Hm vielleicht mal gehört, aber ich habe nun mal nicht theoretische Informatik studiert. Auch was du unter "Zusicherung" verstehst ist mir nicht bekannt - die Prinzipien dürften mir schon bekannt sein, aber nicht unter den Namen)

Die Werte von Attributen ändern sicher nichts an der Tatsache dass das Objekt vom Typ "Unterklasse" und somit auch "Oberklasse" ist

Konstruktoren sind public und somit der Code drin auch.

Der Code wurde unter Anderem in die Funktion init() ausgelagert, weil man die Objekte in den Anfangszustand versetzen will, ohne immer wieder den Konstruktor aufrufen zu müssen. Also macht es auch nur Sinn wenn sie public ist  (Die ist im konkreten Fall sogar ohne Parameter)

Ich bin immer noch am überlegen was an einer setter Methode die Public ist, gefährlich sein soll - wie kann ich da Bugs programmieren?

Wenn du mir jetzt populärwissenschaftlich erklären kannst was daran schlecht sein soll, habe ich heut was gelernt.


----------



## ThreadPool (7. Dez 2010)

Andi_CH hat gesagt.:


> Genau so lese ich aber deinen Einwand. (Liskov? Hm vielleicht mal gehört, aber ich habe nun mal nicht theoretische Informatik studiert. Auch was du unter "Zusicherung" verstehst ist mir nicht bekannt - die Prinzipien dürften mir schon bekannt sein, aber nicht unter den Namen)



Zu Liskov (http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/lsp-liskov-substitutionsprinzip): 
„Wenn es für jedes Objekt u  vom Typ U  ein Objekt o  vom Typ O  gibt, so daß für alle Programme p, die in Operationen des O  definiert wurden, das Verhalten von p  unverändert bleibt, wenn o  durch u  ersetzt wird, dann ist der Typ U  ein Untertyp  des Typs O.“

Salopp ausgedrückt heisst das, überall dort wo du einen Basistyp verwendest solltest du auch einen seiner Subtypen verwenden können ohne das Verhalten des Programmes zu ändern. Das bekommt man in OO Sprachen wie Java über Vererbung und Polymorphie faktisch geschenkt. 

Und jetzt kommt das ABER, durch den obigen Satz lassen sich noch mehr Dinge fordern in Bezug auf die Korrektheit von Programmen. Das geht z.B. von der Frage aus, was passiert wenn Subtypen Methoden überschreiben oder re-implementieren. Also muss man irgendwas im Supertyp festlegen was die Subtypen einhalten müssen.

Im Allgemeinen heissen die Dinger dann Vor-/Nachbedingungen und Invarianten (Klasseninvariante, Schleifeninvariante). Vor- und Nachbedingungen sind einfach gesagt Bedingungen die erfüllt sein müssen bevor ein Stück Code läuft und Bedingungen die erfüllt sein müssen nachdem ein Stück Code gelaufen ist und Invarianten sind Dinge die immer erfüllt sein müssen (aber kurzfristig verletzt werden können innerhalb einer Methode z.B.). Als "rudimentäres" Beispiel in Java können als Vorbedingungen z.B. assert x!= null verstanden werden oder das sich ein integer-Argument innerhalb eines bestimmten Bereichs befindet. Als Nachbedingung z.B. dass wenn du y= x*x rechnest die Nachbedingung x = sqrt(y) ist. 

Diese ganze Sammlung von Konsistenzbedingungen wird dann z.B. im DbC als "Contract" bezeichnet. Für diese Bedingungen gibt es dann weitere Regeln, eine davon ist wie ich schon erwähnte, das Vorbedingungen höchstens aufgeweicht aber nicht verschärft werden dürfen. Bei Klasseninvarianten ist es ähnlich, stell dir vor du legst im Basistyp ein Attribut an welches nur Ganzzahlen in einem bestimmten Bereich erlaubt. In einem Subtyp darfst du den Bereich höchstens weiter einschränken aber nicht erweitern.

Einfacher kann ich es nicht beschreiben, man kann das ganze dann auch noch recht theoretisch angehaucht auswalzen...

EDIT: Die ganzen Diskussionen über das Substitutionsprinzip etc. führen natürlich auch zu Auseinandersetzungen was denn nun "richtig" sei und wann man wie etwas verletzen darf. Umgangsprachlich wird ein Subtyp der das LSP komplett erfüllt als "wahrer" oder echter Subtyp bezeichnet also das was die "ist-ein" Beziehung ausmacht. Den Rest bezeichne ich gerne als "is-like-a", sprich "ist-wie-ein" (d.h. ähnlich aber nicht 100%). Wenn man übrigens die "Client-"Schnittstelle (sehr einfach ausgedrückt also die "public"-Methoden) des Subtyps um Methoden erweitert ist es schon kein "wahrer" Subtyp mehr, das LSP ist da ein wenig streng.


----------



## tfa (7. Dez 2010)

Andi_CH hat gesagt.:


> Genau so lese ich aber deinen Einwand. (Liskov? Hm vielleicht mal gehört, aber ich habe nun mal nicht theoretische Informatik studiert. Auch was du unter "Zusicherung" verstehst ist mir nicht bekannt - die Prinzipien dürften mir schon bekannt sein, aber nicht unter den Namen)


Die Grundbegriffe wie Zusicherung   sollte man schon beherrschen. LSV gehört absolut nicht in die Theoretische Informatik, sondern ist ein ganz praktisches Prinzip der Softwareentwicklung.  Threadpool hat ja schon viel dazu geschrieben.



> Die Werte von Attributen ändern sicher nichts an der Tatsache dass das Objekt vom Typ "Unterklasse" und somit auch "Oberklasse" ist


Rein technisch stimmt das, aber ist die Klassenhierarchie pragmatisch sinnvoll? Darauf kommt es auch an. 
Du kennst das Square-Retangle-Problem? Stell dir vor, du hast die Klasse "Rechteck" mit den Attributen "breite" und "höhe". Jetzt sollst du eine "Quadrat"-Klasse programmieren. Sollte man die von Rechteck ableiten (denn "Quadrat" is-a "Recheck")?
 Wenn ja, müsste Quadrat eine (verschärfende) Zusicherung (siehe Link oben) über Attribute der Oberklasse machen, nämlich breite==höhe. Ein "Quadrat.setBreite()" müsse die Höhe gleich mit anpassen und umgekehrt. Ein solches Quadrat-Objekt verhält sich nicht wie ein Rechteck, obwohl es eins sein soll, das LSV wird verletzt => ein Quadrat ist kein Rechteck. 
Und das ist keine Theorie!



> Der Code wurde unter Anderem in die Funktion init() ausgelagert, weil man die Objekte in den Anfangszustand versetzen will, ohne immer wieder den Konstruktor aufrufen zu müssen. Also macht es auch nur Sinn wenn sie public ist  (Die ist im konkreten Fall sogar ohne Parameter)


Da wir sowieso schon abschweifen, sag ich dazu jetzt mal nichts...


----------



## Andi_CH (7. Dez 2010)

tfa hat gesagt.:


> Ein solches Quadrat-Objekt verhält sich nicht wie ein Rechteck, obwohl es eins sein soll, das LSV wird verletzt => ein Quadrat ist kein Rechteck.
> Und das ist keine Theorie!
> 
> Da wir sowieso schon abschweifen, sag ich dazu jetzt mal nichts...



Da müssen wir definitv nicht gleicher Meinung sein, denn mit diesen Einschränkungen wirst du kaum mehr etwas voneinander ableiten können - aber das ist OOA und OOD


----------



## tfa (8. Dez 2010)

> Da müssen wir definitv nicht gleicher Meinung sein


Naja- immerhin hast du fast vier Minuten drüber nachgedacht...


----------



## maki (8. Dez 2010)

> ... denn mit diesen Einschränkungen wirst du kaum mehr etwas voneinander ableiten können - aber das ist OOA und OOD


Doch, klar kann man bei eEinhaltung dieser Regeln noch ableiten, "Gute" Klassenhierarchien machen genau das.


----------

