Ich beginne mit der Deklaration der einhüllenden Klasse:
class MfDateWrap { private Date _original;
}
Bei dem Hüllen-Ansatz muss ich die Konstruktoren anders aufbauen. Die Origi- nalkonstruktoren werden durch eine einfache Delegation implementiert:
public MfDateWrap (String dateString) { _original = new Date(dateString);
};
Der konvertierende Konstruktor setzt nun die Instanzvariable:
public MfDateWrap (Date arg) { _original = arg;
}
Dann kommt noch die langweilige Aufgabe, alle Methoden der Originalklasse zu delegieren. Ich zeige nur ein paar:
public int getYear() {
return _original.getYear();
}
public boolean equals (MfDateWrap arg) { return (toDate().equals(arg.toDate()));
}
Nachdem dies erledigt ist, kann ich mittels Methode verschieben (139) datumspezi- fisches Verhalten in die neue Klasse verschieben:
client class...
private static Date nextDay(Date arg) { // foreign method, should be on date
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
} wird zu:
class MfDate...
Date nextDay() {
return new Date (getYear(),getMonth(), getDate() + 1);
}
Ein besonderes Problem beim Einsatz von Hüllen besteht im Umgang mit Metho- den, die ein Original als Argument erhalten, wie
public boolean after (Date arg)
Da ich das Original nicht verọndern kann, kann ich after nur in einer Richtung verwenden:
aWrapper.after(aDate) // kann angepasst werden aWrapper.after(anotherWrapper) // kann angepasst werden aDate.after(aWrapper) // wird nicht funktionieren
Die Aufgabe dieser Art von ĩberschreiben ist es, vor den Clients die Tatsache zu verbergen, dass ich eine Hülle verwende. Das ist eine gute Politik, denn der An- wender einer Hülle sollte sich wirklich nicht um die Hülle kümmern müssen und beide gleich behandeln kửnnen. Ich kann diese Information aber nicht ganz ver- heimlichen. Das Problem liegt in einigen Systemmethoden, wie equals. Im Ideal- fall wỹrden Sie erwarten, dass Sie equals in MfDateWrap so ỹberschreiben kửnnten:
public boolean equals (Date arg)// führt zu Problemen
Dies ist aber gefọhrlich, denn obwohl ich es fỹr meine eigenen Zwecke machen kann, nehmen andere Teile des Java-Systems an, dass equals symmetrisch ist: Ist a.equals(b), so auch b.equals(a). Wenn ich diese Regel verletze, tritt eine Reihe sonderbarer Fehler auf. Der einzige Weg, dies zu vermeiden, wọre die Klasse Date zu verọndern, und wenn ich dies kửnnte, wỹrde ich diese Refaktorisierung nicht einsetzen. In solchen Situationen kann ich nicht umhin offenzulegen, dass ich eine Hülle verwende. Für Tests auf Gleicheit bedeutet das einen neuen Namen für die Methode.
public boolean equalsDate (Date arg)
Ich kann es vermeiden, den Typ von unbekannten Objekten prüfen zu müssen, wenn ich Versionen dieser Methode für Date und MfDateWrap zur Verfügung stelle.
public boolean equalsDate (MfDateWrap arg)
Dieses Problem ist bei der Spezialisierung kein Thema, wenn ich die Methode nicht ỹberschreibe. ĩberschreibe ich sie, so komme ich mit der Methodensuche vửllig durcheinander. Ich ỹberschreibe Methoden in Erweiterungen meistens nicht, ich füge nur neue hinzu.
8 Daten organisieren
In diesem Kapitel beschreibe ich einige Refaktorisierungen, die den Umgang mit Daten vereinfachen. Viele Entwickler halten Eigenes Feld kapseln (171) fỹr unnử- tig. Es war lange Thema harmloser Debatten, ob ein Objekt seine Daten direkt oder ỹber Zugriffsmethoden nutzen sollte. Manchmal benửtigen Sie Zugriffsme- thoden, und Sie kửnnen sie mittels Eigenes Feld kapseln (171) bekommen. Ich ver- wende im Allgemeinen den direkten Zugriff, da ich es für einfach halte, diese Re- faktorisierung durchzufỹhren, wenn ich sie benửtige.
Eine der nützlichen Eigenschaften objektorientierter Sprachen ist es, dass sie es Ihnen ermửglichen, neue Typen zu definieren, die ỹber das hinausgehen, was mit den einfachen Datentypen traditioneller Sprachen gemacht werden kann. Es dau- ert allerdings etwas, sich daran zu gewửhnen. Oft beginnen Sie mit einem einfa- chen Datenwert und erkennen dann, dass ein Objekt nỹtzlicher wọre. Wert durch Objekt ersetzen (175) ermửglicht es Ihnen, dumme Daten in klar erkennbare Ob- jekte zu verwandeln. Wenn Sie erkennen, dass diese Objekte Instanzen sind, die Sie in vielen Teilen Ihres Programms benửtigen, so kửnnen Sie Wert durch Referenz ersetzen (179) einsetzen, um daraus Referenzobjekte zu machen.
Wenn Sie sehen, dass ein Array als Datenstruktur verwendet wird, kửnnen Sie die Struktur mit Array durch Objekt ersetzen (186) klarer gestalten. In all diesen Fọllen ist das Objekt aber nur der erste Schritt. Der richtige Vorteil tritt ein, wenn Sie Me- thode verschieben (139) verwenden, um die neuen Objekte mit Verhalten zu verse- hen.
Magische Zahlen, Zahlen mit einer besonderen Bedeutung, waren lange Zeit ein Problem. Ich erinnere mich, in meinen ersten Tagen als Programmierer gelernt zu haben, sie nicht zu verwenden. Sie tauchen aber immer wieder auf, und ich ver- wende Magische Zahl durch symbolische Konstante ersetzen (208), um mich magi- scher Zahlen zu entledigen, wann immer ich herausgefunden habe, was sie leis- ten.
Links zwischen Objekten kửnnen in eine Richtung (unidirektional) oder in bei- den Richtungen (bidirektional) benutzbar sein. Links in nur eine Richtung sind einfacher, aber manchmal benửtigen Sie Gerichtete Assoziation durch bidirektionale ersetzen (199), um eine neue Funktion zu unterstützen. Bidirektionale Assoziation durch gerichtete ersetzen (203) entfernt unnửtige Komplexitọt, wenn Sie feststellen, dass Sie keinen Link in beide Richtungen mehr benửtigen.
Mir sind oft Fọlle begegnet, in denen GUI-Klassen Anwendungslogik enthielten, die dort nicht hingehửrte. Um das Verhalten in die entsprechenden Anwen- dungsklassen zu verschieben, müssen Sie Daten in der Anwendungsklasse haben und die GUI durch Beobachtete Daten duplizieren (190) unterstützen. Ich dupliziere normalerweise Daten nur ungern, aber dies ist eine Ausnahme, die Sie oft nicht vermeiden kửnnen.
Einer der Kernlehrsọtze der objektorientierten Programmierung ist die Kapselung.
Flitzen irgendwo ửffentliche Daten nackt herum, so kửnnen Sie Feld kapseln (209) verwenden, um sie geziemend zu bedecken. Handelt es sich um eine Collection, so verwenden Sie statt dessen Collection kapseln (211), denn diese hat ein spezielles Protokoll. Ist ein gesamter Satz nackt, verwenden Sie Satz durch Datenklasse erset- zen (220).
Eine Form der Daten, die besonderer Behandlung bedarf, sind Typenschlüssel: ein spezieller Wert, der etwas Besonderes über den Typ der Instanz aussagt. Diese er- scheinen họufig als Aufzọhlungstypen (enumerations), oft implementiert als sta- tische finale Integer-Variablen. Dienen die Typenschlüssel nur zur Information und ọndern das Verhalten nicht, so kửnnen Sie Typenschlỹssel durch Klasse ersetzen (221) verwenden. Dies bietet Ihnen eine bessere Typüberprüfung und eine Platt- form, um Verhalten spọter zu verschieben. Wird das Verhalten der Klasse vom Ty- penschlỹssel beeinflusst, verwenden Sie mửglichst Typenschlỹssel durch Unterklas- sen ersetzen (227). Ist dies nicht mửglich, so verwenden Sie das kompliziertere (aber flexiblere) Typenschlüssel durch Zustand/Strategie ersetzen (231).