Law of Demeter

Rudolf

Bekanntes Mitglied
Hi,

ich möchte über das Gesetz von Demeter philosophieren. Er sagt ja im Grunde aus, dass man Objektreferenzen niemals mit zu vielen Punkten benutzen soll, da die Testfälle zu komplex werden.

Was ist aber mit Beanklassen? Eine Bean enthält eine andere Bean und diese Bean enthält ein Datenfeld, auf das ich zufreifen möchte. Eine Bean klasse heißt für mich ein POJO mit getter settern und feldern und nichts weiter.

Gilt Law of Demeter auch für Beans?
 
Zuletzt bearbeitet:
N

nillehammer

Gast
Ich würde sagen eindeutig JA. Abgesehen von dem Einschleichen von Abhängikeiten zu entfernten Objekten, lesen sich meterlange get-Schlangen einfach schlecht. Wobei es evtl. schon noch Auslegungssache ist, wie strikt man sich daran hält (ein indirektes get evtl. noch erlauben, zwei schon zuviel?). Zumindest sollte man nachdenken, ob eine delegier-Methode sinnvoll eingesetzt werden kann. Dazu ein Beispiel (stark verkürzt ohne Initialisierungscode und Behandlung von Sonderfällen):
Java:
// Die standard Bean
public class Kurs {

  private List<Teilnehmer> teilnehmers;

  public List<Teilnehmer> getTeilnehmers() {

   return this.teilnehmers;
  }

}

// Der standard Clientcode
kurs.getTeilnehmer.get(0);

// Die verbesserte Bean
public class Kurs {

  private List<Teilnehmer> teilnehmers;

  // Dieser Getter ist nur noch package-visible
  // Damit nicht mehr Bestandteil des APIs.
  // Könnte man auch ganz rausnehmen.
  // Aber wenn man die Bean mit Hibernate persistieren will,
  // hat man ihn noch dafür.
  List<Teilnehmer> getTeilnehmers() {

    return this.teilnehmers;
  }

  public Teilnehmer getTeilnehmerAt(int index) {
    return this.teilnehmers.get(index);
  }
}

// Der verbesserte Clientcode
kurs.getTeilnehmerAt(1);
Bei der verbesserten Bean ist es für den Client einfacher, das zu bekommen, was er wirklich will. Außerdem wird vor ihm verborgen, dass die Teilnehmer in einer List gespeichert sind. Damit nimmt man ihm die Möglichkeit, in der List herumzufuddeln. Ein Umstieg auf Arrays, Sets oder Maps ist "nur" mit dem Anpassen der getTeilnehmerAt-Methode verbunden. Und zu guter letzt in bester "Clean-Code-Manier" ist der Methodenname viel sprechender.

Wie immer sollte man sich im Vorfeld der Erstellung einer Klasse/Interface die Frage stellen, was braucht mein Client wirklich. Und nur das sollte als Methode ins API hinein.
 
Zuletzt bearbeitet von einem Moderator:

Rudolf

Bekanntes Mitglied
Ja das Beispiel ist tödlich banal und die schwierigen Fälle treten bei solchen einfachen Beispielen nicht auf.

Aber was ist wenn man folgendes hat:

Java:
public class Main {
	public static void main(String[] args) {
		final Student student = new Student();
		final Unternehmen unternehmen = new Unternehmen();
		final Praktikum praktikum = new Praktikum(unternehmen);
		final StudentUnternehmen studentUnternehmen = new StudentUnternehmen(student, unternehmen);
		final Bean bean = new Bean(studentUnternehmen, praktikum);

		System.out.println(bean.studentUnternehmen.student.vorname);
		System.out.println(bean.studentUnternehmen.unternehmen.name);
		System.out.println(bean.praktikum.unternehmen.name);
	}
}

class Bean {
	StudentUnternehmen studentUnternehmen;
	Praktikum praktikum;

	Bean(StudentUnternehmen studentUnternehmen, Praktikum praktikum) {
		this.studentUnternehmen = studentUnternehmen;
		this.praktikum = praktikum;
	}
}

class StudentUnternehmen {
	Student student;
	Unternehmen unternehmen;

	StudentUnternehmen(Student student, Unternehmen unternehmen) {
		this.student = student;
		this.unternehmen = unternehmen;
	}
}

class Praktikum {
	Unternehmen unternehmen;
	String ansprechpartner;
	String abteilung;
	String email;
	Date von;
	Date bis;

	Praktikum(Unternehmen unternehmen) {
		this.unternehmen = unternehmen;
	}
}

class Student {
	String vorname;
	String nachname;
	String gruppe;
}

class Unternehmen {
	String name;
	String strasse;
	String hausnummer;
	String plz;
	String stadt;
	String web;
}

Mal angenommen man speichert Daten. Diese Daten werden herausgelesen und in diese Bean-Struktur abgelegt. Dann übergibt man eine Referenz auf diese Bean anderen Methoden, die die Daten weiterverarbeiten.

Falls man mehrere Referenzen übergibt, verliert man die Struktur und es wird unübersichtlich. Ansonsten......tja keine Ahnung wie man es verbessern könnte. Was könnte man eine solche Struktur im Sinne vom Demeterlaw optimieren?
 
Zuletzt bearbeitet:
J

JohannisderKaeufer

Gast
Das System.out.println(...) ist jetzt ein wenig doof.

Aber angenommen du hättest die Methode
Code:
printStudentUnternehmenStudentVorname(...)

Dann sieht die entweder so aus

Java:
void printStudentUnternehmenStudentVorname(Bean bean){
  String name = bean.studentUnternehmen.student.vorname;
  System.out.println("Vorname: "+name);
}

oder so
Java:
void printStudentUnternehmenStudentVorname(String vorname){
  System.out.println("Vorname: "+vorname);
}

Java:
void printStudentUnternehmen(Student student){
  printStudentUnternehmenStudentVorname(student.vorname);
  printStudentUnternehmenStudentName(student.name);
}
 

Rudolf

Bekanntes Mitglied
Warum ist das doof?

Ich habe das Gesetz von Demeter so verstanden, dass nirgendwo im Code der Aufruf
Java:
bean.studentUnternehmen.student.vorname
stattfinden soll. Und das geschieht auch in deinem Code!

Oder irre ich mich?
 
S

Sym

Gast
Letztlich soll die Nutzung von Klassen klarer werden, was die Wartbarkeit und Testbarkeit erhöht. Wenn du eine solche Struktur (bean.studentUnternehmen.student.vorname) hast, musst Du natürlich irgendwie darauf zugreifen. Das Gesetz beschreibt nun nur wie.
 

Rudolf

Bekanntes Mitglied
Letztlich soll die Nutzung von Klassen klarer werden, was die Wartbarkeit und Testbarkeit erhöht. Wenn du eine solche Struktur (bean.studentUnternehmen.student.vorname) hast, musst Du natürlich irgendwie darauf zugreifen. Das Gesetz beschreibt nun nur wie.

Sry aber ich verstehe kein Wort von dem was du da schreibst....
 
J

JohannisderKaeufer

Gast
Warum ist das doof?

Ich habe das Gesetz von Demeter so verstanden, dass nirgendwo im Code der Aufruf
Java:
bean.studentUnternehmen.student.vorname
stattfinden soll. Und das geschieht auch in deinem Code!

Oder irre ich mich?

Seriously?

Alles in eine Main Methode zu packen und darauf ein paar System.out.println's loslassen ist IMHO etwas praxisfremd.

Ich denke mal der gebrauch einer IDE (Eclipse) sollte geläufig sein.

Dann erkläre ich es nochmal ganz langsam.

Code:
System.out.println(bean.studentUnternehmen.student.vorname);
markieren, Rechtsclick, Refactor, Extract Method. Methodenname auf
Code:
printStudentUnternehmenStudentVorname
setzen und als Ergebnis erhält man dann:
Java:
private void printStudentUnternehmenStudentVorname(Bean bean){
   System.out.println(bean.studentUnternehmen.student.vorname);
}

Ok, schon etwas näher an der Praxis und kommt an das erste Snippet aus meiem ersten Post heran.

Das wesentliche daran ist, dass ein Bean übergeben wird, andererseits nur ein String (Der vorname) der ganz am anderen Ende abgespeichert wurde ausgegeben wird.

Das war das Negativbeispiel, bei dem man das Law of Demeter bricht.


Also ändert man daran was. Ein Vorname soll ausgegeben werden, dann genügt es auch einen einzelnen String, der nur den Vornamen enthält zu übergeben.

Dann sieht die Methode wie folgt aus:

Java:
void printStudentUnternehmenStudentVorname(String vorname){
  System.out.println("Vorname: "+vorname);
}

Das ist wohl vergleichbar mit dem zweiten Snippet aus meinem ersten Post.

Jetzt stellt sich die Frage woher, denn der Vorname kommt. Der kommt dann z.B aus einer Methode, die Angaben über einen Studenten ausgeben soll.

Diese Methode bekommt einen Studenten übergeben und ruft dann z.B. mit dessen Vornamen eine Methode auf, die den Vornamen ausgibt, und mit dem Nachnamen eine Methode die den Nachnamen ausgibt.

Ein Beispiel hierfür:
Java:
void printStudentUnternehmen(Student student){
  printStudentUnternehmenStudentVorname(student.vorname);
  printStudentUnternehmenStudentName(student.name);
}
Kommt wohl auch bekannt vor.

Also das erste war ein Negativbeispiel, dann folgten zwei Positivbeispiele bzgl. Law of Demeter.


Warum das ganze.

Um Methode 1 auf korrektheit zu testen muß ein komplettes Bean, also ein komplexer Objektgraph erzeugt werden.
Für die Methode 2 reicht ein einfacher String. Für Methode 3 genügt ein Student oder ein entsprechender Mock.

Bei den Regeln geht es ums Single Responsability principle, das oft als gut und sinnvoll empfunden wird. Law of Demeter beschreibt Regeln nach denen man Objektiv und automatisiert die Einhaltung von SRP überprüfen kann.

Flapsig ausgedrückt Punkte zählen, was du ja schon geschafft hast. Man muß nur erkennen, daß es sich um das Negativbeispiel gehandelt hat.

Und deshalb ist es halt doof alles in Main-Methode zu hauen, da man sich dort dann um alles kümmern muß.
 

Rudolf

Bekanntes Mitglied
JohannisderKaeufer,

meinst du ernsthaft dass ich den Code, den ich gepostet habe, so in meiner Anwendung nutze? LOL junge junge ich habe ihn auf das Wesentliche gekürzt, damit das Problem sichtbar wird. Ich hätte alles mit getter und settern bestücken können, aber ich dachte die User hier sind weit genug, dass sie erkennen worauf es ankommt.

Damit andere etwas davon haben. xehpuk hat folgenden Refactoringvorschlag gemacht:

Java:
import java.util.Date;

public class Main {
	public static void main(String[] args) {
		final Student student = new Student();
		final Unternehmen unternehmen = new Unternehmen();
		final Praktikum praktikum = new Praktikum(unternehmen);
		final StudentUnternehmen studentUnternehmen = new StudentUnternehmen(student, unternehmen);
		final Bean bean = new Bean(studentUnternehmen, praktikum);

		System.out.println(bean.getStudentUnternehmenStudentVorname());
		System.out.println(bean.getStudentUnternehmenUnternehmenName());
		System.out.println(bean.getPraktikumUnternehmenName());
	}
}

class Bean {
	private StudentUnternehmen studentUnternehmen;
	private Praktikum praktikum;

	Bean(StudentUnternehmen studentUnternehmen, Praktikum praktikum) {
		this.studentUnternehmen = studentUnternehmen;
		this.praktikum = praktikum;
	}

	public Praktikum getPraktikum() {
		return praktikum;
	}

	public void setPraktikum(Praktikum praktikum) {
		this.praktikum = praktikum;
	}

	public StudentUnternehmen getStudentUnternehmen() {
		return studentUnternehmen;
	}

	public void setStudentUnternehmen(StudentUnternehmen studentUnternehmen) {
		this.studentUnternehmen = studentUnternehmen;
	}

	public Student getStudentUnternehmenStudent() {
		return getStudentUnternehmen().getStudent();
	}

	public void setStudentUnternehmenStudent(Student student) {
		getStudentUnternehmen().setStudent(student);
	}

	public String getStudentUnternehmenStudentVorname() {
		return getStudentUnternehmen().getStudentVorname();
	}

	public void setStudentUnternehmenStudentVorname(String vorname) {
		getStudentUnternehmen().setStudentVorname(vorname);
	}

	public String getStudentUnternehmenStudentNachname() {
		return getStudentUnternehmen().getStudentNachname();
	}

	public void setStudentUnternehmenStudentNachname(String nachname) {
		getStudentUnternehmen().setStudentNachname(nachname);
	}

	public String getStudentUnternehmenStudentGruppe() {
		return getStudentUnternehmen().getStudentGruppe();
	}

	public void setStudentUnternehmenStudentGruppe(String gruppe) {
		getStudentUnternehmen().setStudentGruppe(gruppe);
	}

	public Unternehmen getStudentUnternehmenUnternehmen() {
		return getStudentUnternehmen().getUnternehmen();
	}

	public void setStudentUnternehmenUnternehmen(Unternehmen unternehmen) {
		getStudentUnternehmen().setUnternehmen(unternehmen);
	}

	public String getStudentUnternehmenUnternehmenName() {
		return getStudentUnternehmen().getUnternehmenName();
	}

	public void setStudentUnternehmenUnternehmenName(String name) {
		getStudentUnternehmen().setUnternehmenName(name);
	}

	public String getStudentUnternehmenUnternehmenStrasse() {
		return getStudentUnternehmen().getUnternehmenStrasse();
	}

	public void setStudentUnternehmenUnternehmenStrasse(String strasse) {
		getStudentUnternehmen().setUnternehmenStrasse(strasse);
	}

	public String getStudentUnternehmenUnternehmenHausnummer() {
		return getStudentUnternehmen().getUnternehmenHausnummer();
	}

	public void setStudentUnternehmenUnternehmenHausnummer(String hausnummer) {
		getStudentUnternehmen().setUnternehmenHausnummer(hausnummer);
	}

	public String getStudentUnternehmenUnternehmenPlz() {
		return getStudentUnternehmen().getUnternehmenPlz();
	}

	public void setStudentUnternehmenUnternehmenPlz(String plz) {
		getStudentUnternehmen().setUnternehmenPlz(plz);
	}

	public String getStudentUnternehmenUnternehmenStadt() {
		return getStudentUnternehmen().getUnternehmenStadt();
	}

	public void setStudentUnternehmenUnternehmenStadt(String stadt) {
		getStudentUnternehmen().setUnternehmenStadt(stadt);
	}

	public String getStudentUnternehmenUnternehmenWeb() {
		return getStudentUnternehmen().getUnternehmenWeb();
	}

	public void setStudentUnternehmenUnternehmenWeb(String web) {
		getStudentUnternehmen().setUnternehmenWeb(web);
	}

	public Unternehmen getPraktikumUnternehmen() {
		return getPraktikum().getUnternehmen();
	}

	public void setPraktikumUnternehmen(Unternehmen unternehmen) {
		getPraktikum().setUnternehmen(unternehmen);
	}

	public String getPraktikumAnsprechpartner() {
		return getPraktikum().getAnsprechpartner();
	}

	public void setPraktikumAnsprechpartner(String ansprechpartner) {
		getPraktikum().setAnsprechpartner(ansprechpartner);
	}

	public String getPraktikumAbteilung() {
		return getPraktikum().getAbteilung();
	}

	public void setPraktikumAbteilung(String abteilung) {
		getPraktikum().setAbteilung(abteilung);
	}

	public String getPraktikumEmail() {
		return getPraktikum().getEmail();
	}

	public void setPraktikumEmail(String email) {
		getPraktikum().setEmail(email);
	}

	public Date getPraktikumVon() {
		return getPraktikum().getVon();
	}

	public void setPraktikumVon(Date von) {
		getPraktikum().setVon(von);
	}

	public Date getPraktikumBis() {
		return getPraktikum().getBis();
	}

	public void setPraktikumBis(Date bis) {
		getPraktikum().setBis(bis);
	}

	public String getPraktikumUnternehmenName() {
		return getPraktikum().getUnternehmenName();
	}

	public void setPraktikumUnternehmenName(String name) {
		getPraktikum().setUnternehmenName(name);
	}

	public String getPraktikumUnternehmenStrasse() {
		return getPraktikum().getUnternehmenStrasse();
	}

	public void setPraktikumUnternehmenStrasse(String strasse) {
		getPraktikum().setUnternehmenStrasse(strasse);
	}

	public String getPraktikumUnternehmenHausnummer() {
		return getPraktikum().getUnternehmenHausnummer();
	}

	public void setPraktikumUnternehmenHausnummer(String hausnummer) {
		getPraktikum().setUnternehmenHausnummer(hausnummer);
	}

	public String getPraktikumUnternehmenPlz() {
		return getPraktikum().getUnternehmenPlz();
	}

	public void setPraktikumUnternehmenPlz(String plz) {
		getPraktikum().setUnternehmenPlz(plz);
	}

	public String getPraktikumUnternehmenStadt() {
		return getPraktikum().getUnternehmenStadt();
	}

	public void setPraktikumUnternehmenStadt(String stadt) {
		getPraktikum().setUnternehmenStadt(stadt);
	}

	public String getPraktikumUnternehmenWeb() {
		return getPraktikum().getUnternehmenWeb();
	}

	public void setPraktikumUnternehmenWeb(String web) {
		getPraktikum().setUnternehmenWeb(web);
	}
}

class StudentUnternehmen {
	private Student student;
	private Unternehmen unternehmen;

	StudentUnternehmen(Student student, Unternehmen unternehmen) {
		this.student = student;
		this.unternehmen = unternehmen;
	}

	public Unternehmen getUnternehmen() {
		return unternehmen;
	}

	public void setUnternehmen(Unternehmen unternehmen) {
		this.unternehmen = unternehmen;
	}

	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	public String getStudentVorname() {
		return getStudent().getVorname();
	}

	public void setStudentVorname(String vorname) {
		getStudent().setVorname(vorname);
	}

	public String getStudentNachname() {
		return getStudent().getNachname();
	}

	public void setStudentNachname(String nachname) {
		getStudent().setNachname(nachname);
	}

	public String getStudentGruppe() {
		return getStudent().getGruppe();
	}

	public void setStudentGruppe(String gruppe) {
		getStudent().setGruppe(gruppe);
	}

	public String getUnternehmenName() {
		return getUnternehmen().getName();
	}

	public void setUnternehmenName(String name) {
		getUnternehmen().setName(name);
	}

	public String getUnternehmenStrasse() {
		return getUnternehmen().getStrasse();
	}

	public void setUnternehmenStrasse(String strasse) {
		getUnternehmen().setStrasse(strasse);
	}

	public String getUnternehmenHausnummer() {
		return getUnternehmen().getHausnummer();
	}

	public void setUnternehmenHausnummer(String hausnummer) {
		getUnternehmen().setHausnummer(hausnummer);
	}

	public String getUnternehmenPlz() {
		return getUnternehmen().getPlz();
	}

	public void setUnternehmenPlz(String plz) {
		getUnternehmen().setPlz(plz);
	}

	public String getUnternehmenStadt() {
		return getUnternehmen().getStadt();
	}

	public void setUnternehmenStadt(String stadt) {
		getUnternehmen().setStadt(stadt);
	}

	public String getUnternehmenWeb() {
		return getUnternehmen().getWeb();
	}

	public void setUnternehmenWeb(String web) {
		getUnternehmen().setWeb(web);
	}
}

class Praktikum {
	private Unternehmen unternehmen;
	private String ansprechpartner;
	private String abteilung;
	private String email;
	private Date von;
	private Date bis;

	Praktikum(Unternehmen unternehmen) {
		this.unternehmen = unternehmen;
	}

	public Unternehmen getUnternehmen() {
		return unternehmen;
	}

	public void setUnternehmen(Unternehmen unternehmen) {
		this.unternehmen = unternehmen;
	}

	public String getAnsprechpartner() {
		return ansprechpartner;
	}

	public void setAnsprechpartner(String ansprechpartner) {
		this.ansprechpartner = ansprechpartner;
	}

	public String getAbteilung() {
		return abteilung;
	}

	public void setAbteilung(String abteilung) {
		this.abteilung = abteilung;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getVon() {
		return von;
	}

	public void setVon(Date von) {
		this.von = von;
	}

	public Date getBis() {
		return bis;
	}

	public void setBis(Date bis) {
		this.bis = bis;
	}

	public String getUnternehmenName() {
		return getUnternehmen().getName();
	}

	public void setUnternehmenName(String name) {
		getUnternehmen().setName(name);
	}

	public String getUnternehmenStrasse() {
		return getUnternehmen().getStrasse();
	}

	public void setUnternehmenStrasse(String strasse) {
		getUnternehmen().setStrasse(strasse);
	}

	public String getUnternehmenHausnummer() {
		return getUnternehmen().getHausnummer();
	}

	public void setUnternehmenHausnummer(String hausnummer) {
		getUnternehmen().setHausnummer(hausnummer);
	}

	public String getUnternehmenPlz() {
		return getUnternehmen().getPlz();
	}

	public void setUnternehmenPlz(String plz) {
		getUnternehmen().setPlz(plz);
	}

	public String getUnternehmenStadt() {
		return getUnternehmen().getStadt();
	}

	public void setUnternehmenStadt(String stadt) {
		getUnternehmen().setStadt(stadt);
	}

	public String getUnternehmenWeb() {
		return getUnternehmen().getWeb();
	}

	public void setUnternehmenWeb(String web) {
		getUnternehmen().setWeb(web);
	}
}

class Student {
	private String vorname;
	private String nachname;
	private String gruppe;

	public String getVorname() {
		return vorname;
	}

	public void setVorname(String vorname) {
		this.vorname = vorname;
	}

	public String getNachname() {
		return nachname;
	}

	public void setNachname(String nachname) {
		this.nachname = nachname;
	}

	public String getGruppe() {
		return gruppe;
	}

	public void setGruppe(String gruppe) {
		this.gruppe = gruppe;
	}
}

class Unternehmen {
	private String name;
	private String strasse;
	private String hausnummer;
	private String plz;
	private String stadt;
	private String web;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getStrasse() {
		return strasse;
	}

	public void setStrasse(String strasse) {
		this.strasse = strasse;
	}

	public String getHausnummer() {
		return hausnummer;
	}

	public void setHausnummer(String hausnummer) {
		this.hausnummer = hausnummer;
	}

	public String getPlz() {
		return plz;
	}

	public void setPlz(String plz) {
		this.plz = plz;
	}

	public String getStadt() {
		return stadt;
	}

	public void setStadt(String stadt) {
		this.stadt = stadt;
	}

	public String getWeb() {
		return web;
	}

	public void setWeb(String web) {
		this.web = web;
	}
}

Natürlich ist dieses Beispiel auch etwas "fremd", wie er auch festgestellt hat, und löst das Problem mit den Abhängigkeiten nicht. In anderen Quellen habe ich gefunden, dass man bei POJO Klassen nicht auf das Law of Demeter achten muss.

Hat sonst noch jemand Erfahrungen mit dem Law of Demeter und wann man Ausnahmen machen soll?
 
J

JohannisderKaeufer

Gast
meinst du ernsthaft dass ich den Code, den ich gepostet habe, so in meiner Anwendung nutze?

Ja. Sonst hättest du wohl diese Frage nicht gestellt.

LOL junge junge ich habe ihn auf das Wesentliche gekürzt, damit das Problem sichtbar wird.

Das ist mir jetzt nicht aufgefallen.

Ich hätte alles mit getter und settern bestücken können, aber ich dachte die User hier sind weit genug, dass sie erkennen worauf es ankommt.

Aber sicher doch. Und wie ich mich über fehlende getter und setter echauffiert habe.


Mit den System.out.println's brichst du gegen das LoD.
Ich hab dir nur versucht zu erklären
warum du dagegen verstößt
warum es keine gute Idee ist dagegen zu verstoßen
wie man das beheben kann.​

[OT]Pferden beim Kacken zuzuschauen scheint aber interessanter zu sein, als reflektiert zu lesen.[/OT]

[WR]
Der Punkt ist, dass du, wenn du einen Namen ausgeben möchtest ein ganzes Bean brauchst.
Damit brauchst du an der Stelle an der du den Namen ausgibst, Wissen über das gesamte Bean.[/WR]

Und das ist schlechtes Design.

Deine Methoden sehen zwangsläufig immer so aus:

Java:
methodenName(Bean bean){
  bean.ein().ganz().langer().Weg().um().an().einen().einzelnen().Wert().heranzukommen();
}

methodenNameMitNullUeberpruefung(Bean bean){
  if(bean != null){
    Ein ein = bean.ein();
    if(ein != null){
      Ganz ganz = ein.ganz();
      if(ganz != null){
        ganz.langer().Weg().um().an().einen().anderen().Wert().heranzukommen();
      }
    }
  }  
}

Das System.out.println() bekommt in dem Beispiel immer nur ein Bean übergeben auf dem es dann Suchen muß.
Aufgabe von System.out.println() ist es aber einen String auszugeben!


Nenne das Bean doch einfach um, in Context, implementiere es als Singleton oder als public static final. Das sind all die Code-Smells die entstehen. Und wie kann man Sie erkennen.?

Indem man diese einfachen Regeln LoD zur Überprüfung heranzieht.

Es gibt Gründe für Methodchaining bzgl. DSL's, aber die Leute die diese DSL's entwerfen haben in der Regel Ahnung von dem was Sie machen. Und dann dürfen Sie diese LoD-Regeln brechen. Aber nur diese Leute.
 
N

nillehammer

Gast
Rudolph hat gesagt.:
Ja das Beispiel ist tödlich banal und die schwierigen Fälle treten bei solchen einfachen Beispielen nicht auf.
Du hast explizit nach Beans gefragt. Beans sind naturgemäß "tödlich banal". Und zu Deinem geposteten Beispielcode: Der sieht danach aus, als würden dort m:n Beziehungen aus der Datenbankwelt in Java abgebildet. Oder wozu die Klasse "StudentUnternehmen"? Es steht zwar so nicht im Code, aber wenn Student eine Beziehung zu Praktikum hätte und Praktikum zu Unternehmen. Wäre folgendes denkbar:
Java:
// LOD nicht eingehalten
student.getPraktikum().getUnternehmen();

// nach refactoring zur Einhaltung des LOD
student.getPraktikumsUnternehmen()
Auch wieder "tödlich banal", aber so sind Beans eben. Einen weiteren Vorteil hat das noch. Die zweite Variante kann intern die Situation abfrangen, das Praktikum in Sudent evtl. null sein könnte. Bei der ersten Variante muss sich der caller selbst um die null-Prüfung kümmern oder er riskiert eine NullPointerException.

Ansonsten bin ich jetzt raus aus diesem Thread...
 

Rudolf

Bekanntes Mitglied
JohannisderKaeufer, irgendwie bist du mir hier zu trollig. Wenn du nix gescheites bringen kannst, dann halte dich doch einfach raus.

Java:
Ja. Sonst hättest du wohl diese Frage nicht gestellt.

Aha, für eine Frage, darf man keine Minibeispiel konzeptieren, welches den Code auf das Problem beschränkt, es muss aus der Produktion kommt. In welcher Welt lebst du?

Du brauchst mir nicht zu erklären, was das Gesetz von Demeter ist, und was der Zweck ist. Ich habe bereits oben bestätigt, dass ich das Gesetz kenne und die Idee dahinter. Die einzige Frage ist wann Ausnahmen sinnvoll sind. Und falls keine Ausnahmen sinnvoll sind wie man eine Bean gescheit umkonfiguriert.

und ja nillehammer, du hast Recht, die Bean bildet eine Datanbank objektorientert nach. Aber für euch beide, man sollte niemals null im produktiven Code verwenden, sondern immer ein NullObjekt implementieren, das für eine leere Implementierung fehlt. null sollte man nur fürs Testen verwenden.
 
J

JohannisderKaeufer

Gast
Dein Code:
Java:
/**
 * @param title
 */
private void setAppTitle(final String title) {
  if (Document.get() != null) {
    Document.get().setTitle(GWT.getHostPageBaseURL() + Const.Other.TITLE_SEPERATOR + title);
  }
}

Wohl vernünftiger + Demeterkonform

Java:
/**
 * @param title
*/
private void setAppTitle(final String title) {
  Document document = Document.get();                
  if (document != null) {
    document.setTitle(GWT.getHostPageBaseURL() + Const.OTHER_TITLE_SEPERATOR + title);
  }
}

Dein Code (Controller.java)
Java:
@Override
public void handleMenuView(final MenuView menuView) {
  menuView.init();
  menuView.addItem(Controller.this.model.getMenuTitleResources().home());
  menuView.addItem(Controller.this.model.getMenuTitleResources().about());
  menuView.addItem(Controller.this.model.getMenuTitleResources().coding());
}

Schritt 1
Java:
@Override
public void handleMenuView(final MenuView menuView) {
  menuView.init();
  menuView.addItem(model.getMenuTitleResources().home());
  menuView.addItem(model.getMenuTitleResources().about());
  menuView.addItem(model.getMenuTitleResources().coding());
}

Schritt 2
Java:
@Override
public void handleMenuView(final MenuView menuView, MenuTitles titles) {
  menuView.init();
  menuView.addItem(titles.home());
  menuView.addItem(titles.about());
  menuView.addItem(titles.coding());
}

Schritt 3
Java:
@Override
public void handleMenuView(final MenuView menuView, MenuTitles titles) {
  menuView.initWith(titles);
}

<º)))))><
 

Rudolf

Bekanntes Mitglied
JohannisderKaeufer, ich muss mich entschuldigen, du bist nicht so trollig wie ich angenommen habe. Danke für dein Beispiel.

Mal angenommen man hat folgende Struktur

Java:
public class Main {
	public static void main(String[] args) {
		final String result = new A().get().get().get().get();
	}
}

class A {
	B get() {
		return new B();
	}
}

class B {
	C get() {
		return new C();
	}
}

class C {
	D get() {
		return new D();
	}
}

class D {
	String get() {
		return "hallo";
	}
}

Achtung, es ist ein Minibeispiel und wird nicht im produktiven Einsatz genutzt!

Ist es schon komforn nach dem Demetergesetz, wenn ich folgendes Refactoring durchführe:

Java:
public class Main {
	public static void main(String[] args) {
		final A a = new A();
		final B b = a.get();
		final C c = b.get();
		final D d = c.get();
		final String result = d.get();
	}
}

class A {
	B get() {
		return new B();
	}
}

class B {
	C get() {
		return new C();
	}
}

class C {
	D get() {
		return new D();
	}
}

class D {
	String get() {
		return "hallo";
	}
}

Ich vermute mal nein, weil dadurch die Struktur mit den Abhängigkeiten nicht verbessert wird, aber ich gehe mal auf nummer sicher, weil JohannisderKaeufer

Java:
private void setAppTitle(final String title) {
  if (Document.get() != null) {
    Document.get().setTitle(GWT.getHostPageBaseURL() + Const.Other.TITLE_SEPERATOR + title);
  }
}

als Beispiel herangeführt hat und aus Document.get() eine lokale Variable erstellt hat.
 
J

JohannisderKaeufer

Gast
Ausgehend von Gesetz von Demeter ? Wikipedia

Methoden von K selbst
Methoden der Parameter von m
Methoden der mit K assoziierten Objekte
Methoden von Objekten, die m erzeugt

Wobei Klasse K für Main steht und Methode m für main(args).

Untersuche ich mal die Klasse Main

Java:
public class Main {
    public static void main(String[] args) {
        final A a = new A();
        final B b = a.get();
        final C c = b.get();
        final D d = c.get();
        final String result = d.get();
    }
}

Methoden von K selbst

Main hat nur eine Methode main, welche höchstens Rekursiv aufgerufen werden könnte.

Methoden der Parameter von m

Es gibt nur den Parameter args und auch hier wird davon nichts aufgerufen.

Methoden der mit K assoziierten Objekte

Auch sind keine Objekte mit Main assoziiert.

Methoden von Objekten, die m erzeugt

Es wird ein a vom Typ A erzeugt.
Auf diesem a wird get() aufgerufen.
Soweit so gut.
Das liefert uns ein b vom Typ B.
B wurde nicht erzeugt. Also darf darauf keine Methode aufgerufen werden.

Es existiert aber ein Aufruf
Code:
b.get();

Folglich ist das LoD gebrochen.

Das ist die eine Argumentation.

Nun ein paar legale Varianten dazu.

Java:
public class Variante1 {
    public static void main(String[] args) {
        final D d = new D();
        final String result = d.get();
    }
}

Java:
public class Variante2 {
  public static C getCFrom(B b) {
    return b.get();  
  }
  public static D getDFrom(C c) {
    return c.get();
  }
  public static String getStringFrom(D d) { 
    return d.get();
  }

    public static void main(String[] args) {
        final A a = new A();
        final B b = a.get();
        final C c = getCFrom(b);
        final D d = getDFrom(c);
        final String result = getStringFrom(d);
    }
}

Die andere Argumentation kann sein, daß man das get() gleichbedeutend mit dem Erzeugen mittels new setzt.
Also eine Architektur die stark auf Factories baut, dann meine ich dass man auch ein get() dazu zählen kann.

Code:
Document.get()
würde ich in diesem Sinne als Factorymethode sehen (static)und damit Objekterzeugung.
Document hat laut api auch nur einen Protected Consturctor.

Bei einem
Code:
b.get()
hingegen tue ich mich hier schon deutlich schwerer und würde es nicht zur Objekterzeugung hinzuzählen obwohl das einzige was die Methode macht ist ein new C() zurückzuliefern.


Bei dem setTitle Beispiel hätte man auch folgendes Akzeptieren können:

Java:
private void setAppTitle(final String title) {
  Document document = Document.get();                
  if (document != null) {
    setAppTitle(title, document);
  }
}

private void setAppTitle(final String title, Document document) {
  if (document != null) {
    document.setTitle(GWT.getHostPageBaseURL() + OTHER_CONST.TITLE_SEPERATOR + title);
  }
}

Ein Grund warum ich das Beispiel mit dem Document.get() ausgewählt habe ist der dass folgendes vorkommen kann
Code:
Document.get() != Document.get()
Genauso wie
Code:
new C().equals(new C()) != true
ist.

Das führt die Überprüfung von
Code:
if(Document.get() != null)
in der Ursprünglichen Version von setAppTitle() ad Absurdum.

Die Methoden getCFrom(B b), getDFrom(C c) und getStringFrom(D d) sind andererseits gesehen auch nur bloat, die allerdings die Regeln bestehen.

Wenn es nun Sinn macht auf den String von D zuzugreifen, wenn man nur ein A zur Verfügung hat, dann ist auch dies denkbar.
Java:
public class Main {
    public static void main(String[] args) {
        final A a = new A();
        final String result = a.getString();
    }
}

abstract class StringAdapter {
  abstract StringAdapter get();

  String getString(){
    StringAdapter adapter = get();
    return adapter.getString();
  }
}
 
class A extends StringAdapter {
    B get() {
        return new B();
    }
}
 
class B extends StringAdapter {
    C get() {
        return new C();
    }
}
 
class C extends StringAdapter {
    D get() {
        return new D();
    }
}
 
class D extends StringAdapter{
    StringAdapter get(){
      throw new IllegalMethodInvocation();
    }

    String getString() {
        return "hallo";
    }
}

Es sind Regeln, die man einfach (teils automatisiert) überprüfen kann und die erstaunlich oft auf problematische Stellen zeigen. Es bleiben aber immer mehrere Lösungsoptionen offen, so daß man selbst entscheiden muß was man tut.
 

Rudolf

Bekanntes Mitglied
OK,

dann noch eine Frage. Man schaue sich diesen Code an

Java:
public class Main {
	public static void main(String[] args) {
		new Main().test(new DB(), new Object());
	}

	private void test(DB db, Object obj) {
		final String result = klassenMethode(db.getInt(obj));
	}

	private String klassenMethode(int id) {
		return String.valueOf(id);
	}
}

class DB {
	public int getInt(Object obj) {
		return 5;
	}
}

Die Methode test() erfüllt alle Krtierien vom DemeterGesetz. Aber der Gedanke hinter dem Gesetz ist ja, dass eine Klasse nicht mehr über andere Klassen weiß als unbedingt nötig. Man könnte durch Verschachteln von Funktionen ich sage mal syntaktisch die Anforderungen vom DemeterGesetz erfüllen, aber die Klasse weiß dennoch über das Innenleben anderer Klasse bescheid. Ein ausführlicheres Beispiel ist das von mir hier weiter oben gepostete Code von xephuk.

Oder hat das damit nichts zu tun?
 
B

bygones

Gast
ich bin der Meinung hier liegt eine falsche Sichtweise vor. LoD beschreibt die Verwendung vom Client erstmal.

Eine Klasse sollte nicht eine Abhaengigkeit bekommen, um dann ueber alle moeglichen gets an das gewuenschte Objekt zu gelangen, sondern eben genau dieses sollte es als Abhaengigkeit bekommen.

Woher nun der Aufrufer diese Abhaengigkeit bekommt ist erstmal sekundaer.

Java:
public class HumanInfo {
  public void printHairColor(Human human) {
    Color hairColor = human.getBody().getHead().getHair().getColor();
    // not good
  }

 public void printHair(Hair hair) {
   Color hairColor = hair.getColor();
   // good
 }
}
so sehe ich jdf das LoD
 

Rudolf

Bekanntes Mitglied
ich bin der Meinung hier liegt eine falsche Sichtweise vor. LoD beschreibt die Verwendung vom Client erstmal.

Mit Client meinst du die Methoden oder wie?

Also ist mein Beispiel absolut ok so? Ich habe einige Quellen gefunden, u.a. Wikipedia, die nahelegen, dass LoD durch Wrapper-Methoden, die Werte oder Aufrufe an Parameter weiterdeligieren, LoD-komform sind.

Es gibt noch ein anderes Pattern/Prinzip, das nahelegt, Methoden mit mehr als zwei Parametern zu vermeiden. Bei Wrappermethoden wird das aber relativ schwer zu realisieren sein. Besonders bei komplexen Methoden "sammeln" sich die Parameter.

Was meint ihr dazu?
 

ThreadPool

Bekanntes Mitglied
[...]
Was meint ihr dazu?

Dass das alles nur Richtlinien sind und das Essen nie so heiß gegessen wie es gekocht wird. Soll heißen es ist oft eine Frage der Praktikabilität und das man sich nicht sklavisch daran orientieren sollte.
Man muss nämlich auch immer den Kontext beachten in dem solche Richtlinien entstanden sind, z.b. wenn Guru A1,...,An, an einem System B arbeitet muss das nicht heißen das die dabei entstandenen Regeln 100% nützlich für dich sind wenn du an System C bastelst das mit B so viel gemein hat wie ein Sack Reis in China und dem Haarausfall eines Schimpansen im Zoo.
Nichtdestotrotz sollte man sich solche Richtlinien eingehend zu Gemüte führen und über den Sinn und Unsinn reflektieren. Und das LoD kommt bei vielen Dingen schon "automatisch" mit, z.B. wenn man Komposition in Java nachbilden möchte.

Und für alle die den Originaltext jetzt immernoch nicht gelesen haben: http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf
 

Rudolf

Bekanntes Mitglied
Ok mir ist wichtig zu verstehen was der Law of Demeter darüber sagt oder nichts sagt.

In wie fern macht es Sinn mit Wrapperfunktionen zu arbeiten. Also wenn ich richtig verstanden habe, sind solche Funktionen erlaubt. Aber löst man schon damit das Problem mit den Abhängigkeiten? Was meint ihr? Und wäre die Lösung dafür. Wrapperklassen?
 

Network

Top Contributor
[OT]
Ich finde das Gespräch hier sehr interessant.
Wenn man ein Buch liest über OOP dann stehen da immer so extrem tolle theoretische Schlüsse drin wie "Ein Objekt Auto enthält 4 Objekte Reifen" oder "Das Objekt Cabrio ist ein Kind des Objektes Auto".

Macht alles zwar Sinn aber in der Praxis ist OOP halt dann doch irgendwie immer was völlig anderes, wenn dann Klasse Auto Zugriff auf ein Objekt im Array der Klasse SinnloseBodenplatte42 braucht diese aber nunmal eigentlich rein garnichts miteinander zu tun haben "theoretisch".
[/OT]

Network
 
B

bygones

Gast
Mit Client meinst du die Methoden oder wie?
der Aufrufer eben, also derjenige der die Funktionalitaet braucht

Also ist mein Beispiel absolut ok so? Ich habe einige Quellen gefunden, u.a. Wikipedia, die nahelegen, dass LoD durch Wrapper-Methoden, die Werte oder Aufrufe an Parameter weiterdeligieren, LoD-komform sind.
intuitiv klingt das nach Verschleiern des Problems, aber oft ist, wie schon gesagt, zwischen theorie und praxis ein gewaltiger unterschied. Schreib dir Unittests fuer deinen Code und anhand denen bekommt man ein gutes Gefuehl ob es "richtig" ist (aka ob sich die Verwendung des Codes richtig anfuehlt)

Es gibt noch ein anderes Pattern/Prinzip, das nahelegt, Methoden mit mehr als zwei Parametern zu vermeiden. Bei Wrappermethoden wird das aber relativ schwer zu realisieren sein. Besonders bei komplexen Methoden "sammeln" sich die Parameter.
zwei halte ich fuer extrem. Meine Grenze ist meistens 4, danach suche ich nach einem anderen Design
 

Neue Themen


Oben