Definition
Demeter-Prinzip (Law of Demeter)
"Rede nur mit deinen direkten Freunden, nicht mit den Freunden der Freunde".
Auf die OOP bezogen heißt das [1]:
Eine Methode einer Klasse sollte Methoden von Objekten anderer Klassen aufrufen, wenn eine der folgenden Bedingungen gilt:
- Das andere Objekt ist eine Instanzvariable der eigenen Klasse,
- das andere Objekt wird der Methode als Parameter übergeben,
- das andere Objekt wird in der Methode als lokale Variable erzeugt.
Hier ein Zitat aus dem bekannten Clean Code-Buch von Robert C. Martin [2]:
"More precisely, the Law of Demeter says that a method f of a class C should only call the methods of these:
- C
- An object created by f
- An object passed as an argument to f
- An object held in an instance variable of C
The method should not invoke methods on objects that are returned by any of the allowed functions. In other words, talk to friends, not to strangers."
Beispiel aus der Wikipedia
class Motor { public void anlassen() { // den Motor starten. } } class Auto { private Motor motor; public Auto() { motor = new Motor(); } public Motor getMotor() { return motor; } } class Fahrer { public void fahren() { Auto zuFahrendesAuto = new Auto(); zuFahrendesAuto.getMotor().anlassen(); //hier wird gegen das Gesetz verstoßen } }
Die Klasse Fahrer greift hier über die Klasse Auto auf eine Methode der Klasse Motor zu. Dies ist ein Verstoß gegen das Gesetz von Demeter.
Eine Version, die nicht gegen das Demeter-Prinzip verstößt, sähe so aus, dass das Anlassen des Motors in der Klasse Auto selbst als Methode implementiert ist.
zuFahrendesAuto.motorAnlassen();
Hier würde ein Objekt der Klasse Fahrer auf eine Methode eines Auto-Objekts zugreifen, das in der Klasse Fahrer selbst erzeugt wurde (Punkt 3 in der obigen Auflistung).
Beispiel aus dem Projekt "Heterogene Liste"
Die folgenden Ausführungen beziehen sich auf das Java-Beispiel "Heterogener Array". Bevor Sie hier weiterlesen, sollten Sie sich dieses Beispiel gut ansehen.
Betrachten wir nun folgenden Quelltext:
GeoFigur[] figuren = new GeoFigur[3]; figuren[0] = new Kreis(5); figuren[1] = new Quadrat(4); figuren[2] = new Rechteck(3, 6); for (GeoFigur figur : figuren) { System.out.println(figur.gibFlaeche()); }
Hier wird sauber nach dem Demeter-Prinzip, Unterpunkt 3 gearbeitet: In dem Quelltext werden drei Objekte der Klasse GeoFigur bzw. der Unterklassen Kreis, Quadrat und Rechteck als lokale Variablen erzeugt und in einem Array gespeichert. Es werden nur Methoden dieser lokalen Objekte aufgerufen.
Wie sähe hier eine Verletzung des Demeter-Prinzips aus?
In der Klasse Punkt des Java-Projektes gibt es zwei Getter-Methoden getXPos()und getYPos() sowie eine abgeleitete oder indirekte Getter-Methode toString(), die einen String wie "(230,450)" zurückliefert und dabei die Instanzvariablen xPos und yPos auswertet.
Nun wollen wir eine Methode schreiben, die alle x- und y-Positionen der Objekte im Array ausgibt:
public void zeigePositionen()
{
for (int i = 0; i < geoArray.length; i++)
{
System.out.println("Position des " + (i+1) + ". Objekts:");
System.out.println(geoArray[i].getPosition().toString());
}
}
In dem zweiten println()-Befehle wird die Methode toString() des Punkt-Objektes aufgerufen, das von einem Objekt der Klasse GeoFigur geliefert wird. Hier wird quasi "Der Freund eines Freundes" angesprochen - und das verletzt das Demeter-Prinzip. Ich zitiere hier einmal aus dem Buch von Lahres et al. [1]:
"Wenn Sie über diese bereits bekannten Objekte hinaus auf weitere Objekte zugreifen, um mit diesen zu arbeiten, haben Sie neue Abhängigkeiten geschaffen, da Sie nun die Schnittstelle dieser Objekte kennen müssen. Die Kopplung zwischen Modulen wird dadurch verstärkt. Das Demeter-Prinzip wirkt dem entgegen."
Vor- und Nachteile
Hier zitiere ich einfach mal aus dem deutschen Wikipedia-Artikel:
"Bei Anwendung des Gesetzes von Demeter sollte sich durch die geringere Abhängigkeit (Kopplung) der Objekte von der internen Struktur anderer Objekte eine bessere Wartbarkeit, Testbarkeit und Anpassbarkeit der Software (= wesentliche Qualitätskriterien für Software nach ISO/IEC 25000) ergeben.
Andererseits erfordert die Anwendung häufig Vermittler-Methoden (Wrapper), was den initialen Entwicklungsaufwand erhöhen kann, sofern keine automatisierten Werkzeuge zu ihrer Erzeugung eingesetzt werden. Außerdem können Wrapper die Performance geringfügig verschlechtern und den Speicherverbrauch leicht erhöhen"
Train Wrecks
Hier noch ein schlechtes Code-Beispiel von dem Clean Code-Buch:
String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Hier wird die Methode eines Objektes eines Objektes eines Objektes aufgerufen. Martin bezeichnet solche verketteten Aufrufe als "Train Wrecks", weil der Kette von Aufrufen an eine Reihe von Waggons eines Zuges erinnert.
Wird das Demeter-Prinzip nicht eingehalten, wird auch ein wichtiges Grundprinzip der OOP verletzt, nämlich das der Abstraktion.
Wenn man eine bestimmte Java-Klasse wie ArrayList oder HashMap einsetzt, dann sollte die Implementation dieser Klassen völlig irrelevant sein. Es zählt einzig und allein die Spezifikation der Klasse, also die Schnittstelle der Methoden sowie die Vorbedingungen, Nachbedingungen und Invarianten. Als Nutzer einer solchen Klasse abstrahiert man quasi von den inneren Details.
Bei einem Aufruf wie beispielsweise
System.out.println(geoArray[i].getPosition().toString());
blickt man aber tiefer in das System, Details der Implementierung der Klasse GeoFigur werden offenbart, zum Beispiel dass innerhalb der Klasse GeoFigur eine Instanzvariable
Punkt position
existiert, die wiederum eine Methode
String toString()
besitzt. Das widerspricht aber dem Prinzip der Abstraktion.
Quellen:
- Lahres et al.: Objektorientierte Programmierung, Rheinwerk Computing 2021.
- Martin. Clean Code - A Handbook of Agile Software Craftmanship. Pearson Education 2009.