Dienstag, 19. November 2013

Java Beans und Use Cases

Praktisches Beispiel 2.2.2

Artikelübersicht
1. Teil Das Data Transfer Object – Be or not to be.
2. Teil Java Beans und Use Cases.
3. Teil Data Transfer Objects - ein Antipattern?


Im letzten Teil dieser Reihe hatte ich ein Beispiel angekündigt, in dem die Businessobjekte in JavaBeans und UseCase-Objekte getrennt wurden. Dabei sind die Daten im JavaBean konzentriert und das Verhalten in sogenannten UseCases separiert. Diese Implementierung habe ich so erlebt und würde sie gerne mit Eurer Hilfe diskutieren um eine größere Klarheit über das Getane zu erlangen.

Eine gegebene Anwendung hat drei Schichten (Layer) und ein Package außerhalb dieser Schichten, in denen die JavaBeans konzentriert sind. Diese können über alle Schichten verwendet werden.



In einem Kommentar zum letzten Teil dieser Reihe wurde angemerkt, dass die Software durch das Verwenden von DTOs zur Bloatware wird. Sie wird also unvermeidlich aufgebläht und muss zusätzliche Aktionen wie das Mapping verrichten. Außerdem liegt die Datenstruktur doppelt vor. Im oben genannten Beispiel sollte wahrscheinlich genau das verhindert werden.

Innerhalb eines erstellten Domänenmodells verfügen wir letztendlich über eine Reihe von Klassen, die eindeutige Verantwortlichkeiten haben sollten (Single-Responsibility-Prinzip). Diese Klassen fassen Eigenschaften und Verhalten für eine bestimmte Aufgabe bzw. Verantwortlichkeit zusammen. Im obigen Beispiel werden dann Eigenschaften und Verhalten wieder getrennt. Dadurch soll wahrscheinlich erreicht werden, dass außerhalb der Businessschicht keine der anderen Schichten auf das Verhalten zugreifen kann.

Allerdings wird dadurch ein zentrales Prinzip der OOP rückgängig gemacht, nämlich das Prinzip, dass Eigenschaften und Verhalten in einem Verantwortungsbereich zusammengefasst werden. Das könnte man als Rückfall in die prozedurale Programmierung bezeichnen. Wir haben auf der einen Seite dumme Daten und auf der anderen Seite das Verhalten.

Am Ende bleibt die Frage, was reichen wir durch die Schichten? Sollen wir die Prinzipien der OOP aufgeben? Sowohl bei den DTOs als auch bei dieser Art der Implementierung tun wir genau dies. Sind die Vorteile des einen oder anderen so groß, dass wir das tun sollten?

Ein großes Problem der obigen Implementierung war eine fehlende Vermittlung der Gründe dieser Architektur. Die Trennung von Daten und Verhalten führte dazu, dass sie auch im Lösungsansatz oft nicht zusammen gedacht wurden. Dadurch entstanden seltsame Konstrukte, die ohne das Single-Responsibility-Prinzip zu beachten, Daten und Verhalten sammelten. Um das zu verhindern hätte vielleicht eine Notation geholfen. Für eine Domänenklasse Customer z.B. CustomerData und CustomerBehavior. Das durchzuhalten erfordert aber wieder ein erhöhtes Maß an Disziplin.

Bevor Ihr diese Fragen diskutiert, erst einmal den Code vom letzten Mal auf unser heutiges Beispiel angepasst.

Wieder haben wir eine Pseudoview:

 

package pseudoview;

public class PseudoDisplay {
 
 public void show() {
  CustomerUseCase customerUseCase = 
                        new CustomerUseCase();
  List customers = 
                        customerUseCase.getTableItems();
  for (Customer customer: customers) {
   System.out.println(customer.getFirstname() 
                          + " " 
                          + customer.getLastname() 
                          + " hat " 
                          + customerUseCase.calculateAsset(customer));
  }
 }
}



Das Databeans-Package enthält die reinen JavaBeans.

 

package javabeans;

/** Bildet die Daten des Kontos ab */
public class Account {
 /** Kontostand */
 private int accountBalance;
 /** Kontobuchungen */
 private List bookings = new ArrayList();
 
 public int getAccountBalance() {
  return accountBalance;
 }
 
 public Integer getBooking(int index) {
  if (bookings.size() > index && index >= 0) {
   return bookings.get(index);
  }
  throw new IllegalArgumentException("...");
 }
 
 public void setBooking(Integer booking) {
  if ((accountBalance + booking) >= 0 ) {
   accountBalance += booking;
   bookings.add(booking);
  } else {
   throw new IllegalArgumentException("...");
  }
 }

}

/** Bildet die Daten des Kunden ab */
public class Customer {
 /** Vorname des Kunden */
 private final String firstname;
 /** Nachname des Kunden */
 private final String lastname;
 /** Konten des Kunden */
 private List accounts = new ArrayList();

 public Customer(String firstname, String lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
 }

 public String getFirstname() {
  return firstname;
 }

 public String getLastname() {
  return lastname;
 }

 public List getAccounts() {
  return accounts;
 }

 public void addAccount(Account account) {
  accounts.add(account);
 }
}




Und zuletzt die Businessschicht, die das Verhalten in Form von sogenannten UseCase-Klassen enthält.

 

package business;

public class CustomerUseCase {

 /** Gibt das Vermögen des Kunden zurück */
 public int calculateAsset(Customer customer) {
  int asset = 0;
  for (Account account : customer.getAccounts()) {
   asset += account.getAccountBalance();
  }
  return asset;
 }
 
 public List getTableItems() {
  // hier holen wir Kunden z.B. aus einer DB
  List customers = new DBTest().select();
  return customers;
 }
}



Im nächsten Teil möchte ich den Standpunkt diskutieren, Data Transfer Objects sind ein Antipattern.

Die Diskussion in den Xing-Gruppen ergab einige wertvolle Links, die ich nicht vorenthalten will:

"The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together." Martin Fowler

"Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming." Eric Evans

Kritik Martin Fowlers zum anämischen Domänenmodell: AnemicDomainModel

Jason Gorman's Folien aus dem Jahre 2006. Dort gibt es ein Beispiel mit einem Konto. Auf Seite 8 und 9 ein Prozeduraler Entwurf und das OO-Gegenstück mit besserer Class Cohesion. (Hinweis dazu von Rusi Filipov)

Jason Gorman's Folien aus dem Jahre 2006: OO Design Principles & Metrics

"As Uncle Bob Martin and others are keen to point out, the real goal of good OO design is to minimise coupling between the different components in our software." Jason Gorman

"Put the behaviour where the data is." Jason Gorman

Jason Gorman zu The Trouble With OO Design...


vorheriger Post dieses Themas     folgender Post dieses Themas


Print Friendly Version of this page Print Get a PDF version of this webpage PDF

1 Kommentar:

  1. In PseudoDisplay des Teil 1 wird deutlich, was die Fassade leistet, sie besorgt eine Liste von CustomerItemDTOs. Letzte werden anschließend durch iteriert und abgefragt. Die DTOs erscheinen als Resultattypen. Im PseudoDisplay des Teil 2 ist es nicht mehr so übersichtlich. Erst werden die Kunden geholt und dann wird während der Iteration über die Kunden das jeweilige Asset berechnet. CostomerUseCase scheint für zwei verschiedene Sachen verantwortlich zu sein. Das ist nicht so schön. Man sieht es auch daran, dass beide Methoden calculateAsset() und getTableItems() static sein könnten, sie haben keinen Bezug zum this von CostumerUseCase. Ich spreche für das DTO. Meines Erachtens gibt es gute Gründe auch für dumme oder vielleicht naive Daten-Objekte. Als solche modellieren sie z.B. nicht eine komplexe Realität wie "Kunden" sondern eine einfache Realität wie die "Daten des Kunden", also ein Destilat einer Kundenrealität reduziert auf das Nötige in einer realen Datenansammlung (DB, XML oder sonst wie). Und das DTO des Teil 1 beschreibt sehr gut die Ergebniserwartung für einen Usecase42 "Kundenkurzinfos sammeln".

    AntwortenLöschen