PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : OOD-Frage


DocEW
2007-06-28, 09:54:26
Hallo mal wieder! :)
Mir ist vorhin aufgefallen, dass eine abgeleitete (also spezialisierte) Klasse Methoden der Basisklasse nicht mit einem spezialisierten Rückgabewert überschreiben darf. Prominentes Beispiel ist z.B. in Java die Methode Object.clone( ): Sie wird zwar von allen Klassen überschrieben, es wird jedoch immer nur ein Object zurück geliefert und nicht der spezialisierte Typ.
Warum ist das so? Es wäre doch viel logischer, wenn Integer.clone( ) einen Integer liefert. Außerdem würde man doch auch den "Vertrag", der durch die Basisklasse vorgeschrieben ist, einhalten, wenn man ein spezialisiertes Objekt liefert.

Freu' mich auf die Diskussion! ;)

DocEW

Grestorn
2007-06-28, 10:06:31
Hallo mal wieder! :)
Mir ist vorhin aufgefallen, dass eine abgeleitete (also spezialisierte) Klasse Methoden der Basisklasse nicht mit einem spezialisierten Rückgabewert überschreiben darf. Prominentes Beispiel ist z.B. in Java die Methode Object.clone( ): Sie wird zwar von allen Klassen überschrieben, es wird jedoch immer nur ein Object zurück geliefert und nicht der spezialisierte Typ.
Warum ist das so? Es wäre doch viel logischer, wenn Integer.clone( ) einen Integer liefert. Außerdem würde man doch auch den "Vertrag", der durch die Basisklasse vorgeschrieben ist, einhalten, wenn man ein spezialisiertes Objekt liefert.

Freu' mich auf die Diskussion! ;)

DocEW

Der Grund ist, dass sich die Signatur einer abgeleiteten Methode (also Name, Paremeter und Rückgabewert) nicht von der der überschriebenen unterscheiden darf.

Da der Aufrufer im Allgemeinen nicht genau weiß, von welcher spezialisierten Klasse das Objekt instantiiert ist, mit der er die Methode aufrufen will, muss er von der Signatur der Basisklasse ausgehen. Anders funktioniert es nicht.

Das ist zugegeben oft schade und einer der Gründe, warum man bei OO nicht auf downcasts verzichten kann (ein Downcast ist ein cast von einer allgemeineren auf eine spezielle Klasse, also z.B. von "Object" nach "String").

#44
2007-06-28, 10:21:39
Der Grund ist, dass sich die Signatur einer abgeleiteten Methode (also Name, Paremeter und Rückgabewert) nicht von der der überschriebenen unterscheiden darf.

Eigentlich gehört der Rückgabewert nicht zur Signatur... Deshalb kann man auch nicht 2 ansonsten gleiche Methoden mit unterschiedlichem Rückgabewert in einer Klasse unterbringen. Woher sollte die RE auch wissen welche sie nun nehmen soll.

Sorry fürs Klugscheißen :redface:

DocEW
2007-06-28, 10:25:54
Da der Aufrufer im Allgemeinen nicht genau weiß, von welcher spezialisierten Klasse das Objekt instantiiert ist, mit der er die Methode aufrufen will, muss er von der Signatur der Basisklasse ausgehen. Anders funktioniert es nicht.
Das ist genau der Punkt, den ich nicht verstehe!
Wenn ich ein Object habe und clone( ) aufrufe, muss ich ein Object bekommen - klar.
Das ist aber doch auf jeden Fall erfüllt, selbst wenn der Rückgabewert von Integer.clone( ) wieder ein Integer ist!

Trap
2007-06-28, 10:55:22
Mir ist vorhin aufgefallen, dass eine abgeleitete (also spezialisierte) Klasse Methoden der Basisklasse nicht mit einem spezialisierten Rückgabewert überschreiben darf.
In C++ kann und darf man das. Laut Wikipedia in Java auch ab J2SE 5.0

Wenn du nach suchen möchtest: "covariant return type"

Grestorn
2007-06-28, 11:09:51
Das ist genau der Punkt, den ich nicht verstehe!
Wenn ich ein Object habe und clone( ) aufrufe, muss ich ein Object bekommen - klar.
Das ist aber doch auf jeden Fall erfüllt, selbst wenn der Rückgabewert von Integer.clone( ) wieder ein Integer ist!


{
Integer x = new Integer(1);

doSomething(x)
}

void doSomething(Object x)
{
Object y = x.clone();

if(y instanceOf Integer)
{
Integer iY = (Integer)y;
...
}
}


Etwas konstruiert ist das Beispiel, aber geh einfach mal davon aus, die Methode "doSomething" kann min ganz verschiedenen Objekten aufgerufen werden, nicht nur mit Integer-Instanzen.

Woher soll man in der Methode "doSomething" wissen, von welchem Typ das Objekt x ist? Woher soll der Compiler das wissen? In diesem Fall wäre immer ein Cast nötig wenn, auch wenn man das von Trap erwähnte "covariant return type"-Feature nutzt.

Anmerkung: Durch ein durchdachtes Design kann man solche Casts aber meist verhindern. Abfragen mit "instanceOf" können fast immer vermieden werden.

PH4Real
2007-06-28, 11:29:19
Hallo mal wieder! :)
Mir ist vorhin aufgefallen, dass eine abgeleitete (also spezialisierte) Klasse Methoden der Basisklasse nicht mit einem spezialisierten Rückgabewert überschreiben darf. Prominentes Beispiel ist z.B. in Java die Methode Object.clone( ): Sie wird zwar von allen Klassen überschrieben, es wird jedoch immer nur ein Object zurück geliefert und nicht der spezialisierte Typ.
Warum ist das so? Es wäre doch viel logischer, wenn Integer.clone( ) einen Integer liefert. Außerdem würde man doch auch den "Vertrag", der durch die Basisklasse vorgeschrieben ist, einhalten, wenn man ein spezialisiertes Objekt liefert.

Freu' mich auf die Diskussion! ;)

DocEW

Wie Trap schon gesagt hat, geht das ab Java 1.5. Wurde hier auch schonmal irgendwo diskutiert.


public class ExtendedBase extends Base {
@Override
public ExtendedBase getNext() {
return this;
}

public static void main(String[] args) {
// Geht seit 1.5
ExtendedClone clone = new ExtendedClone().clone();
// Geht auch seit 1.5
ExtendedBase base = new ExtendedBase().getNext();
}
}

class Base {
public Base getNext() {
return this;
}
}

class ExtendedClone implements Cloneable {
@Override
public ExtendedClone clone() {
return this;
}
}


EDIT: Das was Grestorn beschreibt ist wieder etwas anders. Aber wie schon gesagt, ist "instanceof" böse ;)... Dieses Problem kann man in Java ganz gut seit 1.5 mit Generics beheben. Siehe Comparable<T> oder Callable<T>...

Monger
2007-06-28, 11:37:54
Also:

1) kovariante Rückgabetypen sind seit Java 5.0 erlaubt
2) fast keine Klasse implementiert clone()
3) clone() ist Böse! Lieber mit Copykonstruktoren arbeiten!

PH4Real
2007-06-28, 11:43:03
Also:

3) clone() ist Böse! Lieber mit Copykonstruktoren arbeiten!

@OT:
Es sollte endlich mal von Sun eine neue Annotation @pöse eingeführt werden, damit man nicht die Übersicht verliert. @deprecated benutzen die ja viel zu selten (hashtable pöse, vector pöse, awt pöse, clonable pöse ...) :D

DocEW
2007-06-28, 12:21:32
Haha, ich habe übersehen, dass das Problem bei mir noch ein bisschen verschachtelter liegt: Mein Rückgabetyp ist eine Map, in der die Werte wie beschrieben eine Vererbungshierarchie bilden! OK, dann muss ich nochmal schauen, ob ich das ganze in einer Klasse schachtele oder so...

@Grestorn: Es ist klar, dass es Fälle gibt, wo man trotzdem um ein Casten nicht herum kommt. Aber es gibt eben auch Fälle, wo es einem durch die kovariante Rückgabewerte erspart bleibt! ;) Daher ist für mich immer noch die Frage, warum es z.B. bei clone( ) nicht angewendet wird.

Danke für die Antworten!

Grestorn
2007-06-28, 13:19:47
Also:

1) kovariante Rückgabetypen sind seit Java 5.0 erlaubt
2) fast keine Klasse implementiert clone()
3) clone() ist Böse! Lieber mit Copykonstruktoren arbeiten!

clone bzw. eine eigene "instantiate" Methode ist unvermeidlich, wenn man Objekte duplizieren will, deren genaue Klasse man nicht kennt.

Alleine für das "Prototyp"-Pattern kommt man da nicht drum herum, und es gibt 1001 andere Beispiele.

clone ist vom Prinzip her nicht böse! Man sollte es nur in den meisten Fällen durch eine etwas spezifiziertere Methode in seiner eigenen Basisklasse ersetzen.

Gast
2007-06-28, 13:50:25
und inwiefern ist das ganze jetzt eine OOD-Frage? Nach meinem Verständnis heißt OOD Object Oriented Design...

DocEW
2007-06-28, 14:17:48
und inwiefern ist das ganze jetzt eine OOD-Frage? Nach meinem Verständnis heißt OOD Object Oriented Design...
Weil ich davon ausgegangen bin, dass es eben nicht möglich ist und nach Design-Alternativen gesucht habe.

Monger
2007-06-28, 15:58:08
clone bzw. eine eigene "instantiate" Methode ist unvermeidlich, wenn man Objekte duplizieren will, deren genaue Klasse man nicht kennt.

clone() ist in "Object" eine protected Methode, also von außen sowieso erstmal nicht ausführbar. Wenn du die Klasse nicht kennst, hilft dir Clone auch nicht weiter.

Grestorn
2007-06-28, 17:36:07
clone() ist in "Object" eine protected Methode, also von außen sowieso erstmal nicht ausführbar. Wenn du die Klasse nicht kennst, hilft dir Clone auch nicht weiter.

Ich muss eine Basisklasse kennen, welche einen eigene Methode deklariert (meist Abstrakt), die prinzipiell das gleiche tut wie clone.

Das braucht man wie gesagt recht häufig. Ein deep copy von Collections ist nur ein Beispiel von vielen.

Monger
2007-06-28, 17:54:08
Das braucht man wie gesagt recht häufig. Ein deep copy von Collections ist nur ein Beispiel von vielen.

Ich glaub, ich bin grad echt blind: ich seh auch bei Collections nirgendwo eine öffentliche clone() Methode. Ich hab mir mal eine ArrayList vorgeknöpft und bin durch alle Vererbungsebenen durchgelaufen, und nur auf der untersten Ebene wird clone() tatsächlich überhaupt implementiert (übrigens als shallow copy), und auf public gesetzt.

Kannst du mir mal ein Beispiel geben, wo du von irgendeiner Collection eine Deep Copy machst?

DocEW
2007-06-28, 18:23:17
@Monger: Für ein deep copy muss ja auch nicht die Collection clone( ) implementieren, sondern die gespeicherten Objekte, oder?

Grestorn
2007-06-28, 18:50:14
Ich glaub, ich bin grad echt blind: ich seh auch bei Collections nirgendwo eine öffentliche clone() Methode. Ich hab mir mal eine ArrayList vorgeknöpft und bin durch alle Vererbungsebenen durchgelaufen, und nur auf der untersten Ebene wird clone() tatsächlich überhaupt implementiert (übrigens als shallow copy), und auf public gesetzt.

Kannst du mir mal ein Beispiel geben, wo du von irgendeiner Collection eine Deep Copy machst?

Ein einfaches Beispiel, jetzt mal auf Basis JRE 1.4 (keine Generics). Aber auch mit 1.5 würde es nicht viel anders aussehen.

Das entscheidende ist, dass man myClone() braucht um die korrekte Instanz der konkreten Klasse anzulegen.

Man sollte sich für dieses Beispiel auch vorstellen, dass es noch viel mehr verschiedene Klassen gibt, die MyBusinessObject spezialisieren und durch das DeepCopy kopiert werden können.

public abstract class MyBusinessObject
{
...
public abstract MyBusinessObject myClone();
...
}

public class MySpecialBusinessObject extends MyBusinessObject
{
/** Copy Constructor
*/
public MySpecialBusinessObject(MySpecialBusinessObject &source)
{
...
}
public MyBusinessObject myClone()
{
return new MySpecialBusinessObject(this);
}
}

/**
* Spezielle Form von LinkedList, die nur MyBusinessObject Instanzen enthält,
* für diese aber ein DeepCopy beherrscht
*/
public class MyLinkedList extends LinkedList
{
...
public MyLinkedList deepCopy()
{
MyLinkedList res = new MyLinkedList();
Iterator it = iterator();
while(it.hasNext())
{
MyBusinessObject obj = (MyBusinessObject)it.next();
add(obj.myClone());
}
}
}


Ein anderes, noch viel prominenteres Beispiel wo man ein solches Konstrukt braucht, ist wie gesagt das Prototype-Pattern (http://en.wikipedia.org/wiki/Prototype_pattern)


@Monger: Für ein deep copy muss ja auch nicht die Collection clone( ) implementieren, sondern die gespeicherten Objekte, oder?

Ganz genau!

Monger
2007-06-28, 19:09:36
@Grestorn: okay, so langsam wird es klarer. Wie gesagt: zumindest nach dem was ich gelernt und gelesen habe, ist clone() und clonable nicht so das wahre vom Ei, und wird heute nicht mehr empfohlen. Zumindest in Java ist die derzeitige Aussage: lieber Copykonstruktor verwenden. Und im Endeffekt machst du ja in deinem Beispiel auch nichts anderes, nur führst du nochmal eine extra Methode ein.

PH4Real
2007-06-28, 20:13:35
Das blöde am Java Cloneable Interface ist eben, dass es ein reines Marker-Interface (noch aus JDK1.0) ist und keine Methode definiert. Sprich, selbst wenn ein Objekt Cloneable implementiert, kann man sich nicht darauf verlassen, dass es eine clone() Methode besitzt.

Deswegen wird heute davon eher abgeraten Cloneable zu verwenden, was natürlich nicht heißt, dass man sein eigenes Konzept für DeepCopy nicht entwerfen kann.

EDIT:

Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.