Konstruktoren mit Parametern gleichen Typs vermeiden

Dieser kleine Tipp soll Ihnen zeigen, wie man Konstruktoren so gestaltet, dass Fehler bei der Verwendung vermieden werden.

Sehen Sie sich bitte zunächst das folgende Negativ-Beispiel an:

package demo;

import java.util.Arrays;
import java.util.List;

public class Tip0011 {

 private static class Person {
  private String firstname;
  private String middlename;
  private String surname;

  public Person(String firstname, String middlename, String surname) {
   this.firstname = firstname;
   this.surname = surname;
  }

  public String getFirstname() {
   return firstname;
  }

  public void setFirstname(String firstname) {
   this.firstname = firstname;
  }

  public String getMiddlename() {
   return middlename;
  }

  public void setMiddlename(String middlename) {
   this.middlename = middlename;
  }

  public String getSurname() {
   return surname;
  }

  public void setSurname(String surname) {
   this.surname = surname;
  }

 }

 public static void main(String[] args) {
  List<Person> persons = Arrays.asList(
    new Person("Robert", "C.", "Martin"),
    new Person("John", "R. R.", "Tolkien"));

  persons.forEach(person -> System.out.println(person.getSurname() + ", " + person.getFirstname() + " " + person.getMiddlename()));

 }

}

Wie schnell kann es hier passieren, dass eine Person so erzeugt wird:

Person person = new Person("Washington", null, "George");

Das gleiche Problem tritt häufig auch bei tabellarischen Daten auf:

private class TableData {

  public TableData(int nColumns, int nRows) {
  …
  }
}

Immerhin wurden in beiden Beispielen die Konstruktor-Parameter gut benannt.

Ein mögliche Lösung wäre, den Konstrukor wegzulassen:

package demo;

import java.util.Arrays;
import java.util.List;

public class Tip0011b {

 private static class Person {
  private String firstname;
  private String middlename;
  private String surname;

  public String getFirstname() {
   return firstname;
  }

  public void setFirstname(String firstname) {
   this.firstname = firstname;
  }

  public String getMiddlename() {
   return middlename;
  }

  public void setMiddlename(String middlename) {
   this.middlename = middlename;
  }

  public String getSurname() {
   return surname;
  }

  public void setSurname(String surname) {
   this.surname = surname;
  }

 }

 public static void main(String[] args) {

  Person uncleBob = new Person();
  uncleBob.setFirstname("Robert");
  uncleBob.setMiddlename("C.");
  uncleBob.setSurname("Martin");

  Person lotr = new Person();
  lotr.setFirstname("John");
  lotr.setMiddlename("R. R.");
  lotr.setSurname("Tolkien");
  List<Person> persons = Arrays.asList(
    uncleBob,
    lotr);

  persons.forEach(person -> System.out.println(person.getSurname() + ", " + person.getFirstname() + " " + person.getMiddlename()));

 }

}

Deutlich merkt man, dass nun der Quelltext länger und anstrengender zu lesen wird.

Von Zeit zu Zeit findet man Lösungen, die Wrapper -Klassen, ala

public  class Surname {}
public  class Firstname {}
public  class Middlename {}

public class TableDimension {
public int nColumns;
public int nRows;
}

verwenden. Die Klasse TableDimension darf dann aber keinen Konstruktor mit zwei Parametern haben, sonst wäre das Problem nur verschoben.

Insgesamt bekommen wir eine außerdem eine Klassen-Inflation.

Wir empfehlen hier das Builder -Entwurfsmuster zu verwenden:

package demo.person;

import java.util.Arrays;
import java.util.List;

public class Person {
 private String firstname; 
 private String middlename;
 private String surname;

 public String getFirstname() {
   return firstname;
  }

  void setFirstname(String firstname) {
   this.firstname = firstname;
  }

  public String getMiddlename() {
   return middlename;
  }

  void setMiddlename(String middlename) {
   this.middlename = middlename;
  }

  public String getSurname() {
   return surname;
  }

  void setSurname(String surname) {
   this.surname = surname;
  }

}
package demo.person;

public class PersonBuilder {
  private Person person = new Person();

  public PersonBuilder firstname(String name) {
   person.setFirstname(name);
   return this;
  }

  public PersonBuilder surname(String name) {
   person.setSurname(name);
   return this;
  }

  public PersonBuilder middlename(String name) {
   person.setMiddlename(name);
   return this;
  }

  public Person build() {
   return person;
  }
 }
package demo;

public class Tip0011c {
 public static void main(String[] args) {

  List<Person> persons = Arrays.asList(
    new PersonBuilder().firstname("Robert")
      .middlename("C.")
      .surname("Martin")
      .build(),
    new PersonBuilder().firstname("John")
      .middlename("R. R.")
      .surname("Tolkien")
      .build());

  persons.forEach(person -> System.out.println(person.getSurname() + ", " + person.getFirstname() + " " + person.getMiddlename()));

 }

}

Beachten Sie, dass wir das package demo.person eingführt und die Setter package-private deklariert haben.

Man findet auch Implementierungen, in der die Klasse Person Setter-artige Methoden hat, die das Person -Objekt zurück liefern. Damit hätte die Klasse aber zwei Zuständigkeits-Bereiche, nämlich Daten-Transport und Erzeugung. Kann man so machen, aber: Dies widerspricht dem Single-Responsibilty -Prinzip! Siehe auch S OLID .