Idee des Patterns

Das Observer-Pattern oder zu Deutsch Beobachter-Muster ist eines der am meisten genutzten und bekanntesten Patterns. In diesem Muster gibt es zwei Akteure: Ein Subjekt, welches beobachtet wird und ein oder mehrere Beobachter, die über Änderungen des Subjektes informiert werden wollen.

Würde man ohne das Pattern eine solche Beobachtung implementieren, so müssten die beobachtenden Objekte in regelmäßigen Abständen das beobachtete Subjekt anfragen, ob sich sein Zustand geändert hat. Durch dieses Vorgehen wird unnötig Rechenzeit verschwendet.

Das Objektdiagramm sähe wie folgt aus, wenn das Observer-Pattern noch nicht angewendet worden ist:

Objektdiagramm ohne Anwendung des Observer-Patterns

Die Idee des Observer-Patterns ist es nun, dem zu beobachtenden Subjekt die Aufgabe aufzutragen, die Beobachter bei einer Änderung über die Änderung zu informieren. Die Beobachter müssen nicht mehr in regelmäßigen Abständen beim Subjekt anfragen, sondern können sich darauf verlassen, dass sie eine Nachricht über eine Änderung erhalten.

Nun sieht das Objekt-Diagramm nach der Anwendung des Observer-Patterns wie folgt aus:

mitObserverPattern

Registrierung, Benachrichtigung

Beobachter müssen sich, bevor Sie von einem Subjekt benachrichtigt werden, bei diesem Subjekt registrieren. Jedes Subjekt verwaltet intern eine Liste von Beobachtern, die es bei einer Änderung seiner selbst nacheinander benachrichtigt. Neben der Methode zum Registrieren wird standardmäßig auch eine Methode zum Deregistrieren angeboten. Über diese Methode können sich Beobachter wieder abmelden, so dass sie aus der internen Liste entfernt werden und nicht mehr benachrichtigt werden.

Den Ablauf des Registrierens und der Benachrichtigung verdeutlicht das folgende Sequenzdiagramm:

Sequenzdiagramm des Observer Patterns

Push und Pull

Wenn sich der Zustand des Subjektes ändert, ist für die meisten Beobachter der neue Zustand des Subjektes interessant. Hier lassen sich nun zwei Strategien umsetzen: Das Subjekt kann entweder den geänderten Zustand schon bei der Benachrichtigung des Beobachters mitsenden (Push-Methode). Oder aber der Beobachter kann, sobald er eine Nachricht erhält, dass sich der Zustand des Subjektes geändert hat, selbst aktiv werden und das Subjekt nach seinem neuen Zustand über einen Methodenaufruf befragen (Pull-Methode). Die Push-Methode hat zum Nachteil, dass es eventuell sein kann, dass das Subjekt Informationen sendet, die der Beobachter nicht verwerten kann oder will. Dies ist vor allen Dingen für sehr große Subjekte, die viele Zustände beinhalten der Fall. Auch muss für diesen Fall ein geeignetes Austauschformat, meist eine eigene Klasse, in deren Objekte der Zustand verpackt wird, definiert werden. Die Methode, dass die Beobachter das Subjekt befragen, hat zum Nachteil, dass entsprechende Methoden im Subjekt definiert werden müssen.

Abstrakte Darstellung als UML2-Klassendiagramm

ObserverPattern

Quellcode in Java

Ohne die Java-API zu nutzen, kann das Observer-Pattern (Beobachter-Muster) wie folgt in Java ausimplementiert werden:

/**
 * Subjekt.java
 */
public interface Subjekt {
	public abstract void addBeobachter(Beobachter beobachter);
	public abstract void removeBeobachter(Beobachter beobachter);
	public abstract void notifyAlleBeobachter();
}
/**
 * Beobachter.java
 */
public interface Beobachter {
	public abstract void update();
}
/**
 * KonkretesSubjekt.java
 */
import java.util.ArrayList;
import java.util.List;
 
public class KonkretesSubjekt implements Subjekt {
 
	List<Beobachter> beobachterList = new ArrayList<Beobachter>();
 
	int state = 0;
 
	@Override
	public void addBeobachter(Beobachter beobachter) {
		this.beobachterList.add(beobachter);
	}
 
	@Override
	public void removeBeobachter(Beobachter beobachter) {
		this.beobachterList.remove(beobachter);
	}
 
	@Override
	public void notifyAlleBeobachter() {
		for (Beobachter beobachter : beobachterList) {
			beobachter.update();
		}
	}
 
	public int getState() {
		return state;
	}
 
	public void setState(int state) {
		this.state = state;
		this.notifyAlleBeobachter();
	}
}
/**
 * KonkreterBeobachter.java
 */
public class KonkreterBeobachter implements Beobachter {
 
	private KonkretesSubjekt konkretesSubjekt;
 
	public KonkreterBeobachter(KonkretesSubjekt konkretesSubjekt) {
		this.konkretesSubjekt = konkretesSubjekt;
 
		// Durchführung der Registrierung beim übergebenen Subjekt
		this.konkretesSubjekt.addBeobachter(this);
	}
 
	@Override
	public void update() {
		int newState = konkretesSubjekt.getState();
		// ...auf neuen Status reagieren
	}
}
/**
 * Client.java
 */
public class Client {
 
	public static void main(String[] args) {
 
		// Erzeugung des Subjektes
		KonkretesSubjekt konkretesSubjekt = new KonkretesSubjekt();
 
		// Erzeugung des Beobachters. Dabei wird
		// das Subjekt übergeben und registriert.
		KonkreterBeobachter konkreterBeobachter = 
                                    new KonkreterBeobachter(konkretesSubjekt);
 
		// Zustandsänderung, Subjekt benachrichtigt
		// daraufhin die Beobachter
		konkretesSubjekt.setState(1);
	}
}

Interfaces vs. Abstrakte Klassen

Die Java-API verfügt über ein Interface Observer und eine abstrakte Klasse Observable, die als Basis für das Observer-Pattern genutzt werden können. Durch diese Umsetzung des Observer-Patterns wird klar, dass das oben genannte Interface Subjekt auch durch eine abstrakte Klasse ersetzt werden kann. Dies abstrakte Klasse Observable innerhalb der Java-API beinhaltet schon Methoden, um Beobachter zu benachrichtigen, so dass Code gespart werden kann. Insgesamt ändert sich das Klassendiagramm wie folgt:

Das Observer-Pattern mit Observable und Observer im JDK

Der Quellcode der die Java-API nutzt, sieht wie folgt aus:

/**
 * KonkretesSubjekt.java
 */
import java.util.Observable;
 
public class KonkretesSubjekt extends Observable {
	int state = 0;
 
	public int getState() {
		return state;
	}
 
	public void setState(int state) {
		this.state = state;
		this.setChanged();
		this.notifyObservers();
	}
}
/**
 * KonkreterBeobachter.java
 */
import java.util.Observable;
import java.util.Observer;
 
public class KonkreterBeobachter implements Observer {
 
	private KonkretesSubjekt konkretesSubjekt;
 
	public KonkreterBeobachter(KonkretesSubjekt konkretesSubjekt) {
		this.konkretesSubjekt = konkretesSubjekt;
 
		// Durchführung der Registrierung beim übergebenen Subjekt
		this.konkretesSubjekt.addObserver(this);
	}
 
	@Override
	public void update(Observable o, Object arg) {
		int newState = konkretesSubjekt.getState();
		// ...auf neuen Status reagieren
	}
}
/**
 * Client.java
 */
public class Client {
	public static void main(String[] args) {
 
		// Erzeugung des Subjektes
		KonkretesSubjekt konkretesSubjekt = new KonkretesSubjekt();
 
		// Erzeugung des Beobachters. Dabei wird
		// das Subjekt übergeben und registriert.
		KonkreterBeobachter konkreterBeobachter = 
                                             new KonkreterBeobachter(konkretesSubjekt);
 
		// Zustandsänderung, Subjekt benachrichtigt
		// daraufhin die Beobachter
		konkretesSubjekt.setState(1);
	}
}

Wichtig ist, dass die setChanged()-Methode aufgerufen wird, bevor die Observer benachrichtigt werden. Wird dies nicht getan, so reagiert die abstrakte Oberklasse Observable nicht auf die Änderung, weil sie denkt, dass sich das Subjekt nicht geändert hat.

Eine negative Seitenerscheinung dieser Implementierung ist es, dass das konkrete Subjekt von keiner weiteren Klasse mehr erben kann als der Klasse Observable. Dadurch muss auf eine Delegation ausweichen, wenn es schon vor der Einführung des Observer-Patterns hier eine Vererbung gegeben hat.

In Java 8 kommen zum Sprachstandard von Java sogenannte Default-Methoden hinzu, die in den Interfaces implementiert werden. Diese Default-Methoden eignen sich jedoch nicht für die Implementierung des Observer-Patterns, da innerhalb der Interfaces keine Klassen-Attribute deklariert werden können.

Hooks als eine Vereinfachung des Patterns für nur einen Observer

Ein Hook ist eine Methode, welche in der Oberklasse einer Klasse mit einem leeren Methodenrumpf implementiert ist. Damit ist die Methode nicht abstrakt, sondern hat nur keinen Code, der bei ihrer Ausführung durchlaufen wird. Eine Unterklasse kann, muss jedoch nicht, diese Methode überschreiben.

Mit einem Hook kann sich eine Unterklasse eines Subjektes wie ein Beobachter verhalten, muss es aber nicht.

In JavaScript wird das Hook-Konzept massiv für Beobachter genutzt, indem dynamisch Methoden von DOM-Objekten überschrieben werden. So kann genau eine Funktion beispielsweise bei einem Klick auf ein DOM-Element aufgerufen werden.

Probleme des Observer-Patterns und deren Lösung durch AOP

Eines der Probleme des Observer-Patterns ist es, dass eventuell bei einer Zustandsänderung einer Modelklasse sehr viele Observer benachrichtigt werden. Die Beobachter brauchen jeder für sich Rechenzeit, um beispielsweise ihre Anzeige aktualisieren zu können, was das Programm verlangsamt. Wenn nun dieser neue angezeigte Zustand nur ein Zwischenzustand ist, wie dies beispielsweise bei einem Hinzufügen von einem Element nach dem anderen in eine Liste geschieht, wäre die Aktualisierung der Anzeige überhaupt nicht nötig gewesen. Dies kann sich negativ auf die Performance des Gesamtprogrammes auswirken, bis sogar dahin, dass das Programm unbenutzbar wird und nur noch “flackert”. Hier gibt AOP Abhilfe. AOP kann den dynamischen Kontrollfluss des Programmes mit Hilfe eines Aspektes überwachen und feststellen, dass ein Kontrollflussblock verlassen worden ist und dass eine Serie von Änderungen abgeschlossen ist. Änderungsmitteilungen können zurückgehalten werden, bis dieser Moment erreicht ist.

Auch das Problem, dass bei einer allgemeinen Implementierung, wie in der Java API vorhanden, eine Vererbung einer abstrakten Klasse geschehen muss, lässt sich mit Hilfe der aspektorientierten Programmierung lösen. So lässt sich die abstrakte Implementierung des Observer-Patters komplett von seiner konkreten Implementierung trennen, ohne Nachteile in Kauf nehmen zu müssen. Dabei wird ein Aspekt erstellt, der sämtliche Observable-Methoden des Observer-Patterns in die entsprechende konkrete Klasse des Observable injiziert.

Eine Weiterentwicklung eines Projektes führt natürlicherweise immer dazu, dass vorhandener Code immer unaufgeräumter wird. Ein Refactoring ist ein Umbau dieses unübersichtlichen Codes in übersichtlicheren. Äußerlich betrachtet scheint dies keinen neuen Nutzen zu bringen. Von der Programmierersicht ist ein Refactoring jedoch essentiell. An dieser Stelle seien einige Motivationen für ein Refactoring aufgezeigt:

Gründe für ein Refactoring und damit einen übersichtlichen Quellcode:

  • Programmierer des Teams können den vorhandenen Code besser verstehen und demzufolge mit diesem besser umgehen. Dies hat mehrere Dinge zur Folge:
  1. Eine bessere Verständnis des Codes führt dazu, dass neue Features schneller implementiert werden können. Demzufolge kann man ein Refactoring des Codes als Vorbereiten des Codes für Neuimplementierungen sehen.
  2. Da eine bessere Übersichtlichkeit entsteht, werden weniger Bugs eingebaut, bzw. vorhandene Bugs werden offensichtlich, da der Code verstanden wird.
  3. Auch können Programmierer, die neu dem Team zustoßen, sich in den schon vorhandenen Code besser einarbeiten.
  • Programmieren ist eine geistig anstrengende Arbeit. Zufriedene Programmierer arbeiten besser.
  1. Gibt es immer wieder Verständnissprobleme mit dem Code – wobei das Programm dann teilweise mühsam debuggt werden muss, um dessen Funktionalität zu verstehen – nähert sich der Programmierer seine Frustationsgrenze. Dies kann in ein inneres Aufgeben münden, wo die Software hoffnungslos verloren ist.
  2. Programmieren muss einen gewissen Spaß bereiten. Vor allen Dingen sollte der Programmierer die Frucht seiner Arbeit sehen und nicht in neu auftretenden Bugs ersticken. Ordentlicher Code gibt Programmierern Erfolgserlebnisse, so dass schnell und zügig entwickelt werden kann. Die Agilität der Software ist so hoch.

    Gründe, die gegen ein Refactoring sprechen:

    • Es muss zusätzliche Zeit investiert werden. Für Programmcode, der nicht weiterentwickelt werden soll und in Zukunft ersetzt werden soll, lohnt sich dies nicht. Bei Projekten, die allerdings noch weiterentwickelt werden, entsteht ein Vielfaches an Zeitersparnis am Ende der Implementierungsphase.
    • Ggf. können durch ein Refactoring Fehler dem Code hinzugefügt werden. Hiergegen ist entgegen zu wirken, indem zunächst Tests entwickelt werden, die den vorhandenen Code auf korrekte Arbeitsweise prüfen. Vor und nach dem Refactoring müssen diese Tests erfolgreich laufen. Positiver Nebeneffekt: Der Code der Tests kann behalten werden und auch für nachfolgende Entwicklungsvorhaben weiter verwendet werden.
    • Es entstehen durch bestimmte Refactorings sehr viele Änderungen im Code beispielsweise durch Umbenennen von Methoden. Resultat ist, dass abschließend Branches des Projektes schwer zusammen zu mergen sind.
    • Programmierer sind evtl. gewöhnt immer an der gleichen Stelle zu schauen, um neue Funktionsmerkmale eines Programms zu implementieren. Evlt. werden diese kurzfristig verwirrt, wenn bekannte Strukturen plötzlich verändert sind.




    Zu Weihnachten 1968 sendeten die Astronauten von Apollo 8 diesen Funkspruch aus dem Orbit des Mondes. Als sie die kalte und verkraterte Mondoberfläche betrachteten, merkten sie, wie privilegiert wir auf unserem Planeten sind, in Gottes wunderbarer Schöpfung zu leben und zitierten die ersten Verse der Bibel. Die volle Geschichte ist zum Beispiel unter Wikipedia nachlesbar.


    Ein weiteres Video hierüber:

    Während meiner Studienzeit entstand folgendes Plakat zum Thema Refactoring, welches das Thema auf einer Seite zusammen fasst. Ein Klick auf das Bild zeigt eine Vollansicht:

    Plakat-Refactoring

    Im folgenden sind die Folien des Vortrages wiedergegeben:

    Refactoring01Refactoring02Refactoring03Refactoring04Refactoring05Refactoring06Refactoring07Refactoring08Refactoring09Refactoring10Refactoring11Refactoring12Refactoring13Refactoring14Refactoring15Refactoring16Refactoring17Refactoring18Refactoring19Refactoring20Refactoring21Refactoring22Refactoring23Refactoring24

    Ein Video über den Kern des Evangeliums, welcher mir sehr am Herzen liegt. Entnommen aus dem Adventskalender von Soulsaver. Danke an diejenigen, die mit viel Mühe gegeben haben, künstlerisch diesen Inhalt wiederzugeben.

    Seit nunmehr einigen Jahren betreibt Google ein Produkt namens “Google App Engine”. Dabei handelt es sich um ein PaaS Cloud Produkt (Platform as a Service) in dem gerade in Mode gekommenen Cloud-Bereich. Google bietet hier eine Möglichkeit an, sowohl Python, J2EE wie auch Go-Programme als Webanwendungen zu deployen. Dahinter steht nicht nur ein Container z.B. für Servlets und JSPs, sondern auch die nötige Infrastruktur in der Datenhaltungsschicht, ein MemoryCache, Cronjobs oder entsprechende dauernd laufende Instanzen wie eine Taskqueue werden einem angeboten. Das gute an diesem Service ist, dass Google ein tägliches Start-Quota verschenkt, so dass sich die App Engine ohne Risiko ausprobieren lässt.

    Dasselbe habe ich getan, um Java-Applikationen zu deployen. Google stellt an dieser Stelle ein relativ komforables Plugin für Eclipse bereit, mit welchem sich die Applikationen sowohl lokal testen lassen als auch direkt per Mausklick in die Cloud hochladen lassen. Dies sieht in Eclipse Juno 4.2 so aus (um den Screenshot zu vergrößern, bitte auf das Bild klicken):

    Der Funktionsumfang ist sehr umfangreich. So lassen sich nicht nur JSPs oder Servlets wie standardmäßig entwickeln, sondern es sind sogar JDO oder gar JPA Features vorhanden. Um eine möglichst kleine Applikation zu testen, wurde eine Testapplikation geschrieben, die zunächst nur Dummyeinträge in den lokalen Big-Table-Storage von Google einfügt, von dem Google ein Gigabyte Freivolumen verschenkt.

    Was bei einer Belastung dieser Applikation auffällt, ist, dass das Freivolumen doch nicht so hoch ist, wie eigentlich angenommen, sondern dass relativ schnell einem die Freianfragen an die Big-Table-Datenbank ausgehen. Es bietet sich dann ein Bild wie das folgende in der Quota-Übersicht von Google:

    Auch ein Lesen lässt schnell den Quota auf Null schrumpfen (hier mit einer größeren Datenbank, die über mehrere Wochen angelegt worden ist):

    Interessanterweise tun sich beim Schreiben von Datensätzen sowohl der veraltete Master/Slave-Store wie auch der High Replication Store nicht viel. Beide sind ungefähr zum gleichen Moment Over-Quota. Die Master/Slave-Engine erreicht das Quota in einem Test bei 3179 Datenbankanfragen, die High Replication nach 3189. Selbstverständlich lässt sich hier dem eingeschränkten Quota mit Geld Abhilfe schaffen.

    Abhilfe von dem Bigtable-Problem schafft das Google Cloud SQL Angebot, welches eine SQL-Datenbank für den Entwickler bereit stellt. Auf diese kann direkt aus der Google App Engine zugegriffen werden. Leider ist diese Datenbanklösung auf einen relativ hohen Tagespreis umgestellt worden.

    Man merkt, dass sich die Google Cloud Lösungen für kleinere nicht so häufig besuchte Projekte sehr gut eignen, vor allen Dingen, wenn es wenig Datenbankabfragen gibt. Wird dieses Produkt jedoch für größere Webseiten eingesetzt, so kann dies relativ schnell sehr teurer werden.

    Große Pluspunkte sammelt die Engine in ihrer Stabilität und Standardkonformität ein. Wenn Sie dabei sind, ein Projekt starten zu wollen, so können Sie getrost mit der Google App Engine anfangen und hierauf entwickeln. Sobald die Zeit kommt, wo Sie aus der App Engine heraus wachsen, können Sie sich entscheiden, diese entweder weiter zu nutzen und einen höheren Preis zu bezahlen und dafür jedoch auch ihre Features wie die Lastverteilung und die Einsparung der Serveradministration zu nutzen, oder Sie können auf ein eigenes Serversystem umziehen. Google App Engine ermöglicht es Ihnen sowohl in der Bigtable-Variante wie auch in der Google Cloud SQL-Variante Ihre Daten jederzeit in und aus der Cloud zu portieren.

    Für mich ein sehr gelungenes Produkt für Start-Up-Projekte. Ich werde weiter dabei bleiben.

    Das Projekt befindet sich innerhalb des Workspaces (beispielsweise hier das Projekt AufgabeAP3):

    Mit einem rechten Mausklick auf das Projekt öffnen Sie das Kontextmenü und wählen „Export“:

    Export

    In dem sich nun öffnenden Fenster wählen Sie „Archive-File“:

    Export Eclipse Archive File

    In dem sich nun öffnenden Fenster ist das Projekt schon vorselektiert. Bitte stellen Sie sicher, dass hier das komplette Projekt selektiert ist:

    Export to ZIP Final Screen

    Wählen Sie nun unter Optionen „Save in zip format“ und „Create directory structure for files“ sowie „Compress the contents of the file”. Dies sollte normalerweise schon voreingestellt sein.

    Vergessen Sie nicht unter „To archive file“ die ZIP-Datei anzugeben, in welche das Projekt gespeichert werden soll.

    Nach dem Klick auf „Finish“ befindet sich das gepackte Projekt im entsprechenden Verzeichnis.

    Durch Rechts-Klick auf den leeren Workspace öffnet sich wieder das Kontekt-Menü. Wählen Sie „Import“ -> „Import…

    Import Import Eclipse

    Im folgenden Fenster wählen Sie „General“ -> „Existing Projects into Workspace

    Existing Projects into Workspace

    Wählen Sie in dem folgenden Fenster „Select Archive File“ und wählen Sie über den Browse-Button rechts daneben das entsprechende ZIP-File aus. Unter „Projects“ werden Ihnen die enthaltenen Projekte in diesem ZIP-File angezeigt:

    Select Archive

    Mit einem Klick auf „Finish“ werden die Projekte importiert.

    Im Folgenden möchte ich gerne einige der Refactoring-Funktionalitäten von Eclipse demonstrieren. Für das Refactoring wird ein JUnit-Test, so wie es Ziel der eXtreme-Programming-Methodik ist, benutzt, um zu prüfen, ob die originale Funktionalität noch vorhanden ist. Die Anleitung demonstriert die Fähigkeiten der Eclipse-Entwicklungsumgebung das Rafactoring mit JUnit zu unterstützen, zeigt jedoch im letzten Schritt auch Grenzen auf, so wieder ein händisches Vorgehen vonnöten ist.

    Vielfach wurden die Sourcen der Anleitung erwünscht. Das Projekt zu Beginn befindet sich in vorRefactoring.zip, das Projekt nach allen Refactoringschritten befindet sich in nachRefactoring.zip.

    /**
     * AdressAusgabe.java
     * Klasse zur Ausgabe eines Adressbucheintrages
     * @author Christoph Tornau
     *
     */
    public class AdressAusgabe {
     
    	public String name;
    	public String vorname;
    	public String strasse;
    	public String plz;
    	public String ort;
     
    	public AdressAusgabe(String name, String vorname, String strasse, String plz, String ort  ) {
    		this.name = name;
    		this.vorname = vorname;
    		this.strasse = strasse;
    		this.ort = ort;
    		this.plz = plz;
    	}
     
    	public String toString()
    	{
    		String output = "";
     
    		output += ("***");
    		output += (name);
    		for (int i= name.length() + 3; i<30; i++)
    			output += ("*");
    		output += ("\n");
     
    		output += ("***");
    		output += (vorname);
    		for (int i= vorname.length() + 3; i<30; i++)
    			output +=("*");
    		output += ("\n");
     
    		output += ("***");
    		output += (strasse);
    		for (int i= strasse.length() + 3; i<30; i++)
    			output += ("*");
    		output += ("\n");
     
    		output += ("***");
    		output += (plz);
    		for (int i= plz.length() + 3; i<30; i++)
    			output +=("*");
    		output += ("\n");
     
    		output += ("***");
    		output += (ort);
    		for (int i= ort.length() + 3; i<30; i++)
    			output +=("*");
    		output += ("\n");
     
    		output += ("***");
    		output += ("");
    		for (int i= "".length() + 3; i<30; i++)
    			output +=("*");
    		output += ("\n");
     
    		return output;
    	}
     
    	/**
    	 * Main method
    	 * @param args
    	 */
    	public static void main(String[] args) {
     
    		AdressAusgabe myAdresse1 = new AdressAusgabe ("Maier","Hans",
    				"Musterstrasse 1","11111","Musterstadt");
    		System.out.println(myAdresse1);
     
    		AdressAusgabe myAdresse2 = new AdressAusgabe ("Gustav","Morgan",
    				"Pappelallee 15","53122","Bonn");
    		System.out.println(myAdresse2);
     
    	}
    }

    An der Beispielklasse fallen uns folgende Bad-Smells sofort auf:

    • Der Code für die Ausgabe ist in mehrfacher Ausführung vorhanden.

    • Anscheindend wurde darauf verzichtet in eine extra Datenklasse zu kapseln. Alles ist in einer Klasse geschrieben. Man sollte auf jeden Fall trennen.

    • Felder können von außen gelesen und geschrieben werden. Es gibt keine Getter- und Setter-Methoden.

    Der Code verfügt über einen JUnit-Test, wie folgender Screenshot zeigt:

    Wir führen nun folgende Refactorings nacheinander durch. Nach den einzelenen Schritten führen wir jedesmal einen Test mit JUnit durch. Ds Refactoring kann nur fortgeführt werden, wenn der Balken grün bleibt. Ansonsten haben wir einen Fehler gemacht. Hier der grüne Balken:

    1. Extract Method:

    Nun entsteht eine neue Methode im Code:

    private String printPart(String output) {
    		output += ("***");
    		output += (name);
    		for (int i= name.length() + 3; i<30; i++)
    			output += ("*");
    		output += ("\n");
    		return output;
    	}

    Wir sehen, dass diese Methode als „private“ deklariert ist und den output-String sowohl bekommt als auch wieder ausgibt. Nach dem Extrahieren der Methode können wir die JUnit-Tests ausführen, um zu prüfen, ob die Programmfunktionalität zerstört worden ist.

    2. Ändern des Methoden-Aufrufs

    Wir wollen den String, welcher übergeben wird, in die Ausgabe statt “name” einbauen. Gleichzeitig wollen wir den Originalstring nicht mehr übergeben:

    Nun gibt es einige Fehlermeldungen:

    Wir ignorieren diese und ändern den Code per Hand um:

    	private String printPart(String stringToPrint) {
     
    		String output = "";
    		output += ("***");
    		output += (stringToPrint);
    		for (int i= stringToPrint.length() + 3; i<30; i++)
    			output += ("*");
    		output += ("\n");
    		return output;
    	}

    Ebenso ändern wir den Methodenaufruf per Hand um in

    output = printPart(name);

    Nun können wir per Hand die hinzugefügte Methode ändern und elimieren so den dupplizierten Code:

    public String toString()
    	{
    		String output = "";
     
    		output += printPart(name);
    		output += printPart(vorname);
    		output += printPart(strasse);
    		output += printPart(plz);
    		output += printPart(ort);
    		output += printPart("");
     
    		return output;
    	}

    Nachdem wir dies durchgeführt haben, müssen wir wiederum die JUnit-Tests ausführen und erhalten als Ergebnis, dass der Code immernoch lauffähig ist.

    3. Neue Klasse erzeugen und Methoden bewegen

    Nun möchten wir gerne eine Datenklasse erzeugen, um die Daten von der Hauptklasse abzutrennen:

    Wir bewegen zunächst die Methode printPart, nachdem wir deren Methoden Signatur etwas geändert haben:

    Unser Code ändert sich automatisch mit

    	public String toString()
    	{
    		String output = "";
     
    		output += Adresse.printPart(name);
    		output += Adresse.printPart(vorname);
    		output += Adresse.printPart(strasse);
    		output += Adresse.printPart(plz);
    		output += Adresse.printPart(ort);
    		output += Adresse.printPart("");
     
    		return output;
    	}

    Leider können wir die restlichen Methoden mit Eclipse nicht automatisch verschieben, weshalb wir sie per Hand verschieben, so dass die neue Klasse wie folgt aussieht:

    public class Adresse {
     
    	public String name;
    	public String vorname;
    	public String strasse;
    	public String plz;
    	public String ort;
     
    	public Adresse(String name, String vorname, String strasse, String plz, String ort  ) {
    		this.name = name;
    		this.vorname = vorname;
    		this.strasse = strasse;
    		this.ort = ort;
    		this.plz = plz;
    	}
     
    	public String toString()
    	{
    		String output = "";
     
    		output += Adresse.printPart(name);
    		output += Adresse.printPart(vorname);
    		output += Adresse.printPart(strasse);
    		output += Adresse.printPart(plz);
    		output += Adresse.printPart(ort);
    		output += Adresse.printPart("");
     
    		return output;
    	}
     
    	public static String printPart(String stringToPrint) {
     
    		String output = "";
    		output += ("***");
    		output += (stringToPrint);
    		for (int i= stringToPrint.length() + 3; i<30; i++)
    			output += ("*");
    		output += ("\n");
    		return output;
    	}
     
    }

    Hier hinkt nun unser JUnit-Test. Der Grund hierfür ist, dass sich der Klassenname der zu prüfenden Klasse geändert hat, da die Funktionalität per Hand verschoben worden ist. In diesem Fall dürfen wir jedoch unseren JUnit-Test anpassen, indem wir dort den Klassennamen ändern.

    Wir setzen die Methode printPart wieder privat.

    Noch immer sind die Felder „public“ Wir wenden Encapsulate Field an.

    Wir führen die JUnit-Tests nochmals durch. Wir sehen, dass der Balken weiter grün ist. Das Refactoring war erfolgreich und wir haben viel schöneren Code erhalten.