Beispiel: Sie sind Automobilhersteller und möchten verschiedene Autos herstellen, welche sich über die Ausstattung differenzieren. Dabei sollen verscheide Modell modelliert werden, wobei jedes Auto die selben Grundfunktionen besitzen soll. Jedes Auto soll natürlich Fahren können und Signalleuchten, wie Blinker, Bremslichter und Scheinwerfer haben.
Vererbung oder Polymorphie
Aufgrund der Bedingung, dass alle Modelle mehrere gemeinsame Methoden haben, liegt es nahe, sich der Vererbung zu bedienen.
Superklasse
Eine abstrakte Superklasse Auto von der sich alle konkreten Subklassen, wie zum Beispiel Model1, Model2 und Model3 ableiten lassen und das selbe Grundverhalten erben.
abstrakte Methoden
Wenn man das Fahren, oder die Funktionen für Blinker, Bremslichter und Scheinwerfer nicht vererbt, sondern neu implementiert, werden diese als abstrakte Methoden deklariert.
Das Model1 soll stärker beschleunigen und schneller fahren als das Model2 und Model2 soll stärker beschleunigen und schneller fahren als Model3. Da eine höhere Höchstgeschwindigkeit auch gleichzeitig eine stärkere Bremswirkung benötigt, müssen wir hierfür die Subklassen fahren() und bremsen() überschreiben.
Auch möchten wir ein Prototypen für ein neues bevorstehendes ModelX modellieren.
Identifiziere Eigenschaften, die sich ändern könnten und trenne diese von denen, die konstant bleiben!
Die Methoden fahren() und bremsen() werden immer vererbt, wenn diese bereits in der Superklasse Auto implementiert sind. Hat man aber diese Methoden in der Superklasse als abstrakt deklariert, dann müssen diese in den Subklassen implementiert werden, auch in der Subklasse des Prototyps, der zum Entwicklungszeitpunkt noch gar nicht fahren kann.
Haben Model2 und Model3 die gleichen Fahreigenschaften, so muss die Methode fahren() doppelt geschrieben werden, auch wenn dies dann einen redundanten Code darstellt. Auch muss Beachtet werden, wenn alle Modelle ein Upgrade bekommen sollten, also eine oder mehre Änderungen in ihrem Fahrverhalten, jede Codeänderungen in allen Klassen durchgeführt werden muss. Dadurch entstehet ein Mehraufwand für die Codewartung und Codeerweiterung.
Ein bestehendes, fest implementiertes Verhalten kann nicht wiederverwendet werden. Wird zu einem späteren Zeitpunkt die Modellreihe erweitert, so müssen fahren() und bremsen zwangsläufig wieder neu implementiert werden. Dabei ist es auch irrelevant, ob das neue Modell die gleichen Eigenschaften wie ein schon bestehendes Modell haben wird.
Auch können dann keine allgemeingültige Aussage über das Verhalten der Modelle getroffen werden, da jedes konkrete Modell für sich selbst fest implementiert wurde.
Durch Änderungen in den Klassen ist es auch nicht möglich, das Verhalten der Modelle zur Laufzeit zu verändern.
Beim entwerfen muss man daher immer darauf achten, ob es in der Zukunft Veränderungen am Code geben könnte. Auch gilt es Entwürfe so zu gestalten, dass Änderungen nur minimale Auswirkungen auf den schon bestehenden Code haben.
Delegation
Wo liegen die Gemeinsamkeiten der Modelle und worin unterscheiden sich diese? Die Methoden fahren() und bremsen() ändern sich, bleiben aber proportional gleich, blinken ist für alle gleich. Daher liegt es nahe, diese Methoden aus den Klassen der Modelle herauszuziehen und in separaten Klassen zu kapseln.
Nun muss jedes Modell nur noch sein Fahrverhaltensobjekt mit der zugehörigen Instanzvariable kennen. Soll das Modell2 fahren, so lässt dieses Modell sein Fahrverhaltensobjekt fahren. Delegation von Verhalten an das Verhaltensobjekt in Java könnte dann wie volgt aussehen:
public void fahren(){
fahrVerhalten.fahren();
}
Interfaces
Damit wir nun beim Fahrverhalten nicht festlegen müssen indem wir auf eine Instanzvariable referenzieren, implementieren wir einfach eine Schnittstelle. Wir definieren also ein Interface für das spezifische Verhalten und lassen die konkreten Verhaltensklassen dieses Interface implementieren.
Instanzvariablentyp BellVerhalten:
private FahrVerhalten fahrVerhalten;
Dank der Polymorphie erlangen wir somit Flexibilität, denn die einzelnen Modelle wissen dadurch nicht mehr, welches Verhalten sie konkret besitzten, aber jedese Modell weiss, dass es fahren() und bremsen() kann. Und wir können nun durch entsprechende Getter und Setter oder Konstruktoren das Verhalten der Modelle zur Design- und zur Laufzeit dynamisch setzen, ohne den Code anzufassen zu müssen.
public class Model3 extends Auto {
//Defaultverhalten: schnelles Fahren
private FahrVerhalten fahrVerhalten = new SchnellesFahren();
private void fahren(){
fahrVerhalten.fahren();
}
private void setFahrVerhalten(FahrVerhalten fahrVerhalten){
this.fahrVerhalten = fahrVerhalten;
}
}
Das Model3 kann nun mit variierenden Fahrverhalten genutzt werden, da das Verhalten entkoppelt ist:
public static void main(String[] args) {
Model3 model3 = new Model3(); //Defaultverhalten
model3.fahrVerhalten(); //schnelles Fahren
model3.setFahrVerhalten(new SchnellstesFahren()); //Verhalten dynamisch setzen
model3.fahrVerhalten(); //schnellstes Fahren, wie Model1
}
- Interface FahrVerhalten:
interface FahrVerhalten {
public void fahren();
}
class schnellesFahren implements FahrVerhalten{
public void fahren() {
System.out.println("schnelles Fahren!");
}
}
class nochSchnellesFahren implements FahrVerhalten {
public void fahren() {
System.out.println("schnelleres Fahren!");
}
}
class schnellstesFahren implements FahrVerhalten{
public void fahren() {
System.out.println("schnellstes Fahren!");
}
}
- Abstrakte Klasse Auto:
public abstract class Auto {
//Instanzvariablen vom Typ des Interfaces. Defaultverhalten
FahrVerhalten fahrVerhalten = new SchnellesFahren();
BlinkVerhalten blinkVerhalten = new blinken();
public void setFahrVerhalten(FahrVerhalten fahrVerhalten) {
this.fahrVerhalten = fahrVerhalten;
}
public void setBlinker(Blinker blinken) {
this.blinken = blinken;
}
public void fahren(){
//Delegation des Verhaltens an Verhaltensobjekt
fahrVerhalten.fahren();
}
public void blinken(){
//Delegation des Verhaltens an Verhaltensobjekt
blinken.blinken();
}
}
- konkrete Autoklasse Model1:
public class Model1 extends Auto {
public Model1(){
setFahrVerhalten(new SchnellstesFahren());
setBlinkVerhalten(new blinken());
}
}
- Beispielkonfiguration für einen Kunden:
public class ClientOrder {
public static void main(String[] args) {
Model2 model2 = new Model2();
model2.fahren(); //standart Fahrverhalten
model2.blinken(); //standart Blinker
// Modelupgrade auf Kundenwunsch
model2.setFahrVerhalten(new SchnellstesFahren());
model2.fahren(); //schnellstes Fahren
model2.setBlinker(new LEDBlinker());
model2.blinken(); //LED Blinker
//...
}
}
Antworten