Die Kunst des Software Design - Java

Post on 11-Jan-2015

2.680 views 1 download

description

Folien zu einem internen Vortrag bei der 1&1 Internet AG, der auf Folien der PHP World 2009 basiert.

Transcript of Die Kunst des Software Design - Java

Die Kunst des Software-Design

oder was Software-Entwickler von der Tierwelt lernen können

1&1 Internet AG, 8. Februar 2011Holger Rüprich und Stephan Schmidt

Holger Rüprich

• Head of Sales Processes Access bei der 1&1 Internet AG

• Clean Code Enthusiast

• Gast-Dozent an der Berufs-Akademie Mosbach

• Autor für die Zeitschrift T3N

Stephan Schmidt

• Head of Web Sales Development bei der 1&1 Internet AG

• Design Patterns Enthusiast

• Autor von PHP Design Patterns sowie anderen Büchern und über 30 Fachartikeln

• Redner auf internationalen Konferenzen

Software Design

„Software Design is a process of problem solving and planning for a software solution.“

Wikipedia

„Das Wort Kunst bezeichnet im weitesten Sinne jede entwickelte Tätigkeit, die auf Wissen, Übung, Wahrnehmung, Vorstellung und Intuition gegründet ist.“

Wikipedia

Kunst

Ziele von Software Design

Stabilität Sicherheit Flexibilität Performance

Das Eichhörnchen

Kapsle Daten und Algorithmen.

Von der realen Welt ...

• Bob hat eine Autovermietung

• Er muss Autos einkaufen

• Er vermietet unterschiedliche Modelle

• Er vermietet Autos zu verschiedenen Preisen

... zur programmierten Welt

• Klassen übertragen Dinge aus der realen Welt in die programmierte Welt

public class Car {

private String manufacturer; private String color; private float milage; private boolean engineStarted;

public void startEngine() {} public void driveForward(float miles) {} public void stopEngine() {} public String getManufacturer() {} public String getColor() {} public float getMilage() {}

}

Kapsle den Zugriff auf Daten

Kapsle den Zugriff auf Daten immer innerhalb einer Klasse und biete Methoden an, um diese Daten abzufragen.

public class Car {

... Eigenschaften und Methoden ...

public double getDailyRate(int days) { return 75.5; }}

Kapsle den Zugriff auf Daten

Kapsle den Zugriff auf Daten immer innerhalb einer Klasse und biete Methoden an, um diese Daten abzufragen.

public class Car {

... Eigenschaften und Methoden ...

public double getDailyRate(int days) { if (days >= 7) { return 65.9; } return 75.5; }}

Bobs Kunden

public class Customer { private int id; private String name; public Customer(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; }

}

Bobs Firma

public class RentalCompany {

Map<String, Vehicle> fleet = new HashMap<String, Vehicle>();

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); }

public RentalAction rentVehicle(Vehicle vehicle, Customer customer) {} public boolean returnVehicle(Vehicle vehicle) {}

}

• Klassen übertragen nicht nur physikalische Dinge, sondern auch Abläufe

Bobs Geschäft

public class RentalAction { ... Eigenschaften ...

public RentalAction(Vehicle vehicle, Customer customer) { this(vehicle, customer, new Date()); }

public RentalAction(Vehicle vehicle, Customer customer, Date date) { this.vehicle = vehicle; this.customer = customer; this.rentDate = date; }

... Getter ...}

• Klassen übertragen nicht nur physikalische Dinge, sondern auch Abläufe

Bobs Geschäft

public class RentalAction { ...

public void markCarReturend() { markCarReturend(new Date()); } public void markCarReturend(Date date) { returnDate = date; }

public boolean isReturend() { return returnDate != null; }}

Kapsle auch Algorithmen

Kapsle nicht nur Daten sondern auch Algorithmen in den Methoden deiner Klassen, um komplexe Operationen zentral an einer Stelle zu implementieren.

public class RentalCompany {

...

public RentalAction rentVehicle(Vehicle vehicle, Customer customer) { if (!fleet.containsValue(vehicle)) { throw new UnknownVehicleException(); }

if (!vehicleIsAvailable(vehicle)) { throw new VehicleNotAvailableException(); }

RentalAction rentalAction = new RentalAction(vehicle, customer); rentalActions.add(rentalAction); return rentalAction; }

}

Bobs Firma

Bobs Firma

public class RentalCompany {

...

private boolean isVehicleAvailable(Vehicle vehicle) {

for (RentalAction rentalAction : rentalActions) {

if (rentalAction.getVehicle().equals(vehicle) && !rentalAction.isReturend()) { return false; }

}

return true; }

}

Bobs Firma

public class RentalCompany {

...

public boolean returnVehicle(Vehicle vehicle) {

for (RentalAction rentalAction : rentalActions) {

if (rentalAction.getVehicle().equals(vehicle) && !rentalAction.isReturend()) { rentalAction.markCarReturend(); return true; }

}

return false; }

}

Schütze deine Daten.Verberge Implementierungsdetails und unterbinde den Zugriff auf interne Datenstrukturen.

Wähle Klassen- und Methodennamen sinnvoll und achte darauf, dass der resultierende Code sich wie ein Satz lesen lässt.

Die Weisheit des Eichhörnchens

Das Krokodil

Achte das Single-Reponsibility-Prinzip.

„There should never be more than one reason for a class to change“

Robert C. Martin

Das Single-Responsibility-Prinzip

Bob will wissen was los ist

public class RentalCompany {

Map<String, Vehicle> fleet = new HashMap<String, Vehicle>();

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); System.out.println("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

public void rentVehicle(Vehicle vehicle, Customer customer) { System.out.println("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); }

}

> Neues Auto im Fuhrpark: BMW > Neuer Mietvorgang: Stephan Schmidt leiht BMW > Rückgabe: Stephan Schmidt gibt BMW zurück > Neuer Mietvorgang: Gerd Schaufelberger leiht BMW

public class RentalCompany {

Map<String, Vehicle> fleet = new HashMap<String, Vehicle>();

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); System.out.println("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

public void rentVehicle(Vehicle vehicle, Customer customer) { System.out.println("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); }

}

> Neues Auto im Fuhrpark: BMW > Neuer Mietvorgang: Stephan Schmidt leiht BMW > Rückgabe: Stephan Schmidt gibt BMW zurück > Neuer Mietvorgang: Gerd Schaufelberger leiht BMW

Was ist mit Problemen im Produktivbetrieb?

Bob will wissen was los ist

Debugging im Produktivbetrieb

public class RentalCompany {

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); switch (DEBUG_MODE) { case ECHO: System.out.println("Neues Auto im Fuhrpark: "+vehicle.getManufacturer()); break; case LOG: default: logger.info("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); break; } }

public void rentVehicle(Vehicle vehicle, Customer customer) { switch (DEBUG_MODE) { case ECHO: System.out.println("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); break; case LOG: default: logger.info("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); break; } }

public class RentalCompany {

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); switch (DEBUG_MODE) { case ECHO: System.out.println("Neues Auto im Fuhrpark: "+vehicle.getManufacturer()); break; case LOG: default: logger.info("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); break; } }

public void rentVehicle(Vehicle vehicle, Customer customer) { switch (DEBUG_MODE) { case ECHO: System.out.println("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); break; case LOG: default: logger.info("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); break;

Code Duplizierung macht die Anwendung langsamer und schwerer zu warten

Debugging im Produktivbetrieb

Wiederverwendbarkeit statt Copy-and-Paste

public class RentalCompany {

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); debug("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

public void rentVehicle(Vehicle vehicle, Customer customer) { debug("Neuer Mietvorgang: " + customer.getName() + " leiht " + vehicle.getManufacturer()); }

...

}

Wiederverwendbarkeit statt Copy-and-Paste

public class RentalCompany { ...

private void debug(String message) { switch (DEBUG_MODE) { case ECHO: System.out.println(message); break; case LOG: default: logger.info(message); break; } }}

public class RentalCompany { ...

private void debug(String message) { switch (DEBUG_MODE) { case ECHO: System.out.println(message); break; case LOG: default: logger.info(message); break; } }

Weitere Debugging-Ziele, wie E-Mails oder SMS, machen die debug()-Methode komplexer

und somit fehleranfälliger.

Wiederverwendbarkeit statt Copy-and-Paste

Atomare Probleme lösen

public abstract class RentalCompany { ... Eigenschaften und Methoden der Klasse ...

protected abstract void debug(String message);}

public class EchoingRentalCompany extends RentalCompany { protected void debug(String message) { System.out.println(message); }}

public class LoggingRentalCompany extends RentalCompany { protected void debug(String message) { Logger.getAnonymousLogger().info(message); }}

Atomare Probleme lösen

RentalCompany company;

switch (DEBUG_MODE) {case ECHO: company = new EchoingRentalCompany(); break;case LOG:default: company = new LoggingRentalCompany(); break;}

Car bmw = new Car("BMW", "blau");Customer stephan = new Customer(1, "Stephan Schmidt");Customer gerd = new Customer(2, "Gerd Schaufelberger");

company.addToFleet("bmw1", bmw);company.rentVehicle(bmw, stephan);company.returnVehicle(bmw);

RentalCompany company;

switch (DEBUG_MODE) {case ECHO: company = new EchoingRentalCompany(); break;case LOG:default: company = new LoggingRentalCompany(); break;}

Car bmw = new Car("BMW", "blau");Customer stephan = new Customer(1, "Stephan Schmidt");Customer gerd = new Customer(2, "Gerd Schaufelberger");

company.addToFleet("bmw1", bmw);company.rentVehicle(bmw, stephan);company.returnVehicle(bmw);

Die debug()-Methode steht nur Unterklassen von RentalCompany zur Verfügung.

Atomare Probleme lösen

Komposition statt Vererbung

public interface Debugger { void debug(String message);}

public class DebuggerEcho implements Debugger { { public void debug(String message) { System.out.println(message); }}

public class DebuggerLog implements Debugger { public void debug(String message) { Logger.getAnonymousLogger().info(message); }}

Komposition statt Vererbung

class RentalCompany { ... Eigenschaften der Klasse ...

public RentalCompany() { switch (DEBUG_MODE) { case ECHO: debugger = new DebuggerEcho(); break; case LOG: default: debugger = new DebuggerLog(); break; } }

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); debugger.debug("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

...}

Die Weisheit des Krokodils

Teile und herrsche.Jedes Modul soll genau eine Verantwortung übernehmen, und jede Verantwortung soll genau einem Modul zugeordnet werden.

Die Schildkröte

Achte das Open-Closed-Prinzip.

Das Open-Closed-Prinzip

„Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.“

Bertrand Meyer

Komposition statt Vererbung

class RentalCompany { ... Eigenschaften der Klasse ...

public RentalCompany() { switch (DEBUG_MODE) { case ECHO: debugger = new DebuggerEcho(); break; case LOG: default: debugger = new DebuggerLog(); break; } }

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); debugger.debug("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

...}

class RentalCompany { ... Eigenschaften der Klasse ...

public RentalCompany() { switch (DEBUG_MODE) { case ECHO: debugger = new DebuggerEcho(); break; case LOG: default: debugger = new DebuggerLog(); break; } }

public void addToFleet(String id, Vehicle vehicle) { fleet.put(id, vehicle); debugger.debug("Neues Auto im Fuhrpark: " + vehicle.getManufacturer()); }

...}

Die RentalCompany Klassen ist von anderen Klassen abhängig

Komposition statt Vererbung

Einsatz von Interfaces

public class RentalCompany { ... Eigenschaften der Klasse ...

public RentalCompany(Debugger debugger) { this.debugger = debugger; }

... weitere Methoden der Klasse ...}

Debugger debugger = new DebuggerEcho();RentalCompany company = new RentalCompany(debugger);

Die Weisheit der Schildkröte

Schütze deinen Code.Programmiere immer gegen Schnittstellen, nie gegen eine konkrete Implementierung.

Vermeide feste Abhängigkeiten zwischen einzelnen Klassen deiner Anwendungen und ziehe immer lose Kopplung der Klassen vor.

Der Erpel

Achte das Hollywood-Prinzip.

Das Hollywood Prinzip

„Rufen Sie uns nicht an, wir werden Sie anrufen.“

Das Hollywood Prinzip

Das Hollywood Prinzip

• Auch bekannt als „Inversion of Control“

• Objekte sollen sich ihre Abhängigkeiten nicht selbst holen oder erzeugen

• Objekte sollen unabhängig von ihrer Umgebung sein

Dependency Injection

• Objekte erstellen abhängige Objekte nicht selbst

• Abhängige Objekte werden von außen über den Konstruktor (Constructor Injection) oder über Setter-Methoden (Setter Injection) injiziert

• Abhängige Objekte sind dadurch sehr leicht austauschbar

Dependency Injection

Debugger debugger = new DebuggerEcho();RentalCompany company = new RentalCompany(debugger);

Logger logger = new DateTimeLogger();RentalCompany company.setLogger(logger);

• RentalCompany weiß weder, welche Implementierung des Debugger-Interface noch welche Implementierung des Logger-Interface sie verwendet

• RentalCompany steuert nicht, wie sie an ihre Abhängigkeiten kommt, der Kontrollfluss wird von außerhalb gesteuert

• Aber: Sehr viel zusätzlicher Client Code, durch Instanziieren und Injizieren der Objekte nötig

Constructor Injection

Setter Injection

Inversion-of-Control-Container

• Verringern den nötigen Boilerplate-Code

• Code, der nur nötig ist, um die Objekte zu „verdrahten“

• In der Java-Welt schon weit verbreitet

• Spring, Google Guice, Tapestry IoC, ...

Google Guice

• Basiert komplett auf Java-Code

• Keine externe Konfiguration

• Stattdessen Einsatz von Annotations

• Sehr leichtgewichtig

class RentalCompany { ... Eigenschaften der Klasse ...

@Inject public void setLogger(Logger logger) { this.logger = logger; }

...

}

Rufen Sie Bob nicht an, Bob ruft sie an.

Setter Injection

• Typen werden durch Modul an konkrete Implementierungen gebunden:

public class RentalCompanyModule extends AbstractModule { @Override protected void configure() { bind(Debugger.class).to(DebuggerEcho.class); }}

• Guice kann Injector liefern, der Instanzen erzeugt:

Injector injector = Guice.createInjector(new RentalCompanyModule());Debugger debugger = injector.getInstance(Debugger.class);debugger.debug("Debugging with Guice.");

// Injiziert Debugger auch in andere ObjekteRentalCompany company = injector.getInstance(RentalCompany.class);Car bmw = new Car("BMW", "silver");company.addToFleet("bmw", bmw);

Interfaces binden

public interface Formatter { public String format(String s);}

public class ReverseFormatter implements Formatter { public String format(String s) { return new StringBuffer(s).reverse().toString(); }}

public class UppercaseFormatter implements Formatter { public String format(String s) { return s.toUpperCase(); }}

Komplexeres Beispiel

• Neuer Typ „Formatter“, inkl. Implemetierungen:

public class DebuggerEcho implements Debugger { Formatter formatter;

@Inject public DebuggerEcho(Formatter formatter) { this.formatter = formatter; }

@Override public void debug(String message) { System.out.println(this.formatter.format(message)) }}

Komplexeres Beispiel

• Einsatz in DebuggerEcho:

public class MyApplication { private Debugger debugger; private Formatter formatter;

@Inject public MyApplication(Debugger debugger) { this.debugger = debugger; }

@Inject public void setFormatter(Formatter f) { formatter = f; }

public void doSomething(String s) { s = this.formatter.format(s); System.out.println("Sending '" + s + "' to Debugger."); this.debugger.debug(s); }}

Komplexeres Beispiel

• Und Einsatz in MyApplication:

public class MyApplication { private Debugger debugger; private Formatter formatter;

@Inject public MyApplication(Debugger debugger) { this.debugger = debugger; }

@Inject public void setFormatter(@Named("myFormatter") Formatter f) { formatter = f; }

public void doSomething(String s) { s = this.formatter.format(s); System.out.println("Sending '" + s + "' to Debugger."); this.debugger.debug(s); }}

Komplexeres Beispiel

• Benannte Injections:

public class MyApplicationModule extends AbstractModule {

@Override protected void configure() { bind(Debugger.class).to(DebuggerEcho.class); bind(Formatter.class).to(UppercaseFormatter.class); bind(Formatter.class).annotatedWith(Names.named("myFormatter")). to(ReverseFormatter.class); }}

Komplexeres Beispiel

• Und jetzt die Bindings dazu:

Injector injector = Guice.createInjector(new MyApplicationModule());MyApplication app = injector.getInstance(MyApplication.class);app.doSomething("Hollywood");

• Und jetzt die Bindings dazu:

Komplexeres Beispiel

Was wird jetzt ausgegeben?

Sending 'doowylloH' to Debugger.DOOWYLLOH

Komplexeres Beispiel

• Ausgabe des Beispiels

MyApplication

ReverseFormatter

DebuggerEcho UppercaseFormatter

• Verwenden einer Standardimplementierung.

• Objekte in einem Scope erzeugen, z.B. Singleton.

• Typen an Provider binden, die dann die tatsächlichen Objekte benötigen.

• Primitive Typen binden.

• Unterstützung für eigene Binding-Annotations.

Weitere Features von Guice

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="...">

<bean id="application" class="net.schst.tiercode.ioc.spring.MyApplication"> <constructor-arg ref="echo-debugger"/> <property name="formatter" ref="reverse-formatter"/> </bean>

<bean id="reverse-formatter" class="net.schst.tiercode.ioc.spring.ReverseFormatter"> </bean>

<bean id="uppercase-formatter" class="net.schst.tiercode.ioc.spring.UppercaseFormatter"> </bean>

<bean id="echo-debugger" class="net.schst.tiercode.ioc.spring.DebuggerEcho"> <constructor-arg ref="uppercase-formatter"/> </bean>

</beans>

Das selbe nochmal, mit Spring

• Konfiguration über XML:

ApplicationContext context = new ClassPathXmlApplicationContext("spring/spring-beans.xml");MyApplication app = context.getBean("application", MyApplication.class);app.doSomething("Hollywood");

Das selbe nochmal, mit Spring

• Erzeugen der Beans

Sending 'doowylloH' to Debugger.DOOWYLLOH

• Ausgabe:

<defines> <tag name="EchoDebugger" type="net.schst.tiercode.ioc.xjconf.DebuggerEcho" setter="setDebugger"/>

<tag name="ReverseFormatter" type="net.schst.tiercode.ioc.xjconf.ReverseFormatter" setter="setFormatter"/>

<tag name="UppercaseFormatter" type="net.schst.tiercode.ioc.xjconf.UppercaseFormatter" setter="setFormatter"/>

<tag name="Application" type="net.schst.tiercode.ioc.xjconf.MyApplication"/></defines>

Dasselbe nochmal, mit XJConf

• Tag-Definitionen über XML:

• Nicht ganz dasselbe:

• XJConf ist schlecht bei Constructor-Injection. Also alles auf Setter-Injection umgestellt.

<?xml version="1.0" encoding="UTF-8"?><configuration> <Application> <EchoDebugger> <UppercaseFormatter/> </EchoDebugger> <ReverseFormatter/> </Application></configuration>

Dasselbe nochmal, mit XJConf

• Definition der Objekte über XML:

DefinitionParser tagParser = new DefinitionParser();NamespaceDefinitions defs = tagParser.parse(".../xjconf/defines/defines.xml");

XmlReader conf = new XmlReader();conf.setTagDefinitions(defs);

conf.parse("src/main/resources/xjconf/conf/conf.xml");MyApplication app = (MyApplication) conf.getConfigValue("Application");app.doSomething("Hollywood");

Dasselbe nochmal, mit XJConf

• Erzeugen der Objekte:

Sending 'doowylloH' to Debugger.DOOWYLLOH

• Ausgabe:

Verwende ein DI Framework.Erleichtere dir das Verwalten von komplexen Objektstrukturen durch den Einsatz eines Dependency Injection Frameworks.

Die Weisheit des Erpel

Die Biene

Nutze Design Patterns.

Konkretes Problem

„Ich möchte Debug-Meldungen auf verschiedene Arten verarbeiten und diese auswechseln können, ohne den Code der RentalCompany Klasse anpassen zu müssen.“

Bob

Abstraktes Problem

„Ich möchte eine Aufgabe mit verschiedenen Algorithmen lösen können. Jede der Lösungen soll gekapselt sein und nichts von den anderen wissen. Die einzelnen Lösungen sollen gegeneinander austauschbar sein, ohne den nutzenden Client anzupassen.“

Abstract Bob

Konkret vs Abstrakt

Abstrakt Konkret

AufgabeVerarbeiten von Debug-

Meldungen

AlgorithmenAusgeben per print(),

Schreiben eines Logfiles

ClientDie Klasse

RentalCompany

Konkret vs Abstrakt

Abstrakt Konkret

AufgabePersistieren von

Gästebucheinträgen

AlgorithmenSpeichern in Datenbank, Speichern in XML-Datei

Client Die Klasse Guestbook

Bobs erstes Design Pattern

Strategy-Pattern

Design Patterns

• Lösungsmuster für häufig auftretende Entwurfsaufgaben in der Software- Entwicklung

• Keine Code-Bibliothek

• Organisiert in Pattern-Katalogen (z.B. Gang-of-Four Buch)

• Verschiedene Kategorien: Erzeugungsmuster, Strukturmuster, Verhaltensmuster, Enterprise-Patterns

Erzeugungsmuster

• Erzeugungsmuster werden verwendet, um Objekte zu konstruieren.

• Zu den Erzeugungsmustern gehöhren unter anderem

• Singleton-Pattern

• Factory-Method-Pattern

• Prototype-Pattern

• Abstract-Factory-Pattern

Factory-Method-Pattern

• Definiert eine Schnittstelle zur Erzeugung von Objekten

• Verlagert die eigentliche Instanziierung in Unterklassen

• Lässt Unterklassen entscheiden, welche konkrete Implementierung verwendet wird

Factory-Method-Pattern

public abstract class AbstractManufacturer<T extends Vehicle> { protected String name; public AbstractManufacturer(String name) { this.name = name; } public T sellVehicle() { return manufactureVehicle(); } protected abstract T manufactureVehicle();

}

Factory-Method-Pattern

public class CarManufacturer extends AbstractManufacturer<Car> {

protected Car manufactureVehicle() { return new Car(name, "black"); }

}

CarManufacturer bmwManufacturer = new CarManufacturer("BMW");Car bmw = bmwManufacturer.sellVehicle();

Factory-Method-Pattern

Das Factory-Method-Pattern definiert eine Schnittstelle zur Erzeugung von Objekten. Es verlagert aber die eigentliche Instanziierung in Unterklassen; es lässt die Unterklassen entscheiden, welche konkreten Implementierungen verwendet werden.

Factory-Method-Pattern

Das Factory-Method-Pattern | 177

Definition des Patterns

Das Fabrikmethoden-Muster definiert eine Schnittstelle zur Erzeugung von Objekten. Es

verlagert aber die eigentliche Instanziierung in Unterklassen; es lässt die Unterklassen ent-

scheiden, welche konkreten Implementierungen verwendet werden.

Um dieses Ziel zu erreichen, müssen Sie die folgenden Schritte durchführen:

1. Implementieren Sie eine abstrakte Klasse, in der Sie eine oder mehrere abstrakte

Methoden deklarieren, die die Schnittstelle zum Erzeugen von Objekten vorgeben.

2. Fügen Sie dieser Klasse weitere Methoden hinzu, die Logik enthalten, die bei allen

konkreten Implementierungen identisch sind. Sie können in diesen Methoden be-

reits auf die abstrakte Fabrikmethode zugreifen.

3. Bilden Sie beliebig viele Unterklassen, in denen Sie verschiedene Implementierungen

der abstrakten Methode einfügen.

4. Verwenden Sie nun diese konkreten Unterklassen, um die tatsächlichen Objekte zu

instanziieren und Ihren Applikationscode von den konkreten Implementierungen zu

lösen.

Wann immer Sie eine Fabrikmethode verwenden möchten, achten Sie einfach darauf, die

hier gezeigten Schritte durchzuführen, und dem Erfolg Ihres Vorhabens steht nichts mehr

im Weg. Abbildung 4-2 zeigt Ihnen die Beziehungen zwischen den Beteiligten des Fac-

tory-Method-Patterns und illustriert noch einmal, wie das Pattern auf die Erzeugung der

Vehicle-Implementierungen angewandt wurde.

Abbildung 4-2: UML-Diagramm des Factory-Method-Patterns

+FactoryMethod() : Product

ConcreteCreator

+FactoryMethod() : Product+MethodA()

Creator

ConcreteProduct

Product

Vehicle-Interface

AbstractManufacturer-Klasse

Fabrikmethode manufactureVehicleerzeugt Car- bzw. Convertible-Instanzen

sell Vehicle-Methode, ruftFabrikmethode auf

CarManufacturer undConvertibleManufacturer

Implementierungen des Vehicle-Interfaces wie Car, Convertible etc.

!!"""#$#"%&'()*+,,-../0120.344..56012789.:*.;7<=76.)!!:..33>'&.33

Prototype-Pattern

• Das Prototype-Pattern erzeugt Objekte durch das Kopieren eines prototypischen Exemplars

• Es ermöglicht das Hinzufügen neuer "Klassen" zur Laufzeit ohne Programmierung

• Es hält die Anzahl der benötigten Klassen klein

Prototype-Pattern

public class SpecialEditionManufacturer { protected Map<String, Vehicle> prototypes = new HashMap<String, Vehicle>(); public void addSpecialEdition(String edition, Vehicle prototype) { prototypes.put(edition, prototype); } public Vehicle manufactureVehicle(String edition) throws UnknownSpecialEditionException {

if (prototypes.containsKey(edition)) { return prototypes.get(edition).clone(); }

throw new UnknownSpecialEditionException( "No prototype for special edition " + edition + " registered."); }

}

Prototype-Pattern

SpecialEditionManufacturer manufacturer = new SpecialEditionManufacturer();

Car golfElvis = new Car("VW", "silber");golfElvis.setAirConditioned(true); golfElvis.setGraphics("Gitarre"); manufacturer.addSpecialEdition("Golf Elvis Presley Edition", golfElvis);

Convertible golfStones = new Convertible("VW", "rot"); golfStones.setAirConditioned(false); golfStones.setGraphics("Zunge");manufacturer.addSpecialEdition("Golf Rolling Stones Edition", golfStones);

Vehicle golf1 = manufacturer.manufactureVehicle("Golf Elvis Presley Edition");Vehicle golf2 = manufacturer.manufactureVehicle("Golf Rolling Stones Edition");

Prototype-Pattern

Das Prototyp-Muster bestimmt die Arten der zu erzeugenden Objekte durch die Verwendung eines prototypischen Exemplars, das zur Erzeugung neuer Instanzen kopiert wird.

Prototype-Pattern

200 | Kapitel 4: Erzeugungsmuster

Der Einsatz des Prototype-Patterns reduziert die Anzahl der Klassen, die Ihre Applikation

benötigt, da Sie weniger Unterklassen bilden müssen, um die einzelnen Produkte zu

erzeugen. Stattdessen werden alle Produkte auf Basis der Prototypen von einer Klasse

erzeugt.

FallstrickeDas Prototype-Pattern verlangt lediglich, dass alle Ihre Klassen, aus denen Prototypen

gebildet werden sollen, geklont werden können. Dies erscheint auf den ersten Blick

jedoch einfacher, als dies in der Realität oft der Fall ist. Solange Ihre Prototypen ledig-

lich skalare Werte in ihren Eigenschaften speichern, reicht die Verwendung des clone-

Operators aus, aggregieren Ihre Prototypen jedoch weitere Objekte, müssen Sie eine

eigene __clone()-Methode implementieren, die sich um das Erstellen von Kopien für

diese Objekte kümmert.

Im folgenden Beispiel speichert eine Instanz eines Autos nicht nur die Information, ob

eine Klimaanlage vorhanden ist, sondern stattdessen direkt die Instanz dieser Klimaan-

lage. Für das Beispiel reicht eine konkrete Implementierung für alle Klimaanlagen aus, in

einer echten Anwendung würden Sie hier sicher ein Interface sowie verschiedene kon-

krete Implementierungen einsetzen. Die Klimaanlage speichert im Beispiel nur die Grad-

zahl, auf die sie eingestellt ist:

namespace de\phpdesignpatterns\vehicles\addons;

class AirCondition {

protected $degrees = 20;

Abbildung 4-6: UML-Diagramm des Prototype-Patterns

Vehicle-Interface

SpecialEditionManufacturerkennt alle verfügbarenPrototypen

__clone()-Methodein PHP nicht immer nötig

Implementierungen des Vehicle-Interface wieCar, Convertible etc.

!!"""#$#"%&'()*+,,-../0120.)!!..34012567.8*.95:;54.)!!8..<<='&.<<

Strukturmuster

• Strukturmuster befassen sich mit der Komposition von Objekten

• Zu den Strukturmustern gehören unter anderem

• Composite-Pattern

• Proxy-Pattern

• Adapter-Pattern

• Facade-Pattern

Composite-Pattern

• Lässt mehrere Instanzen eines Typs nach außen wie eine Instanz aussehen

• Implementieren einer neuen Klasse, die die einzelnen Instanzen aufnimmt

• Muss die selbe Schnittstelle implementieren wie die entsprechenden Instanzen

Composite-Pattern

public interface Debugger { void debug(String message);}

public class DebuggerEcho implements Debugger { { public void debug(String message) { System.out.println(message); }}

public class DebuggerLog implements Debugger { public void debug(String message) { Logger.getAnonymousLogger().info(message); }}

Composite-Pattern

public class CompositeDebugger implements Debugger {

protected List<Debugger> debuggers = new ArrayList<Debugger>(); public void addDebugger(Debugger debugger) { debuggers.add(debugger); } public void debug(String message) { for (Debugger debugger : debuggers) { debugger.debug(message); } }

}

CompositeDebugger compositeDebugger = new CompositeDebugger();compositeDebugger.addDebugger(new DebuggerEcho());compositeDebugger.addDebugger(new DebuggerLog());

Composite-Pattern

Das Composite-Pattern fügt mehrere Objekte zu einer Baumstruktur zusammen und ermöglicht es, diese von außen wie ein einzelnes zu verwenden.

Composite-Pattern

212 | Kapitel 5: Strukturmuster

Weitere Anwendungen

Mit der Debugging-Funktionalität haben Sie bereits eine sehr beliebte Anwendung des

Kompositum-Patterns kennengelernt. So bietet das PEAR-Paket Log2, das eine ähnliche

Funktionalität wie das Debugging-Beispiel bereitstellt, auch eine Klasse, die als Komposi-

tum fungiert.

Eine weitere Anwendung findet das Kompositum zum Beispiel in Authentifizierungs-Fra-

meworks. Stellen Sie sich vor, in Ihrem System authentifizieren Sie Benutzer gegen den

firmeninternen LDAP-Server. Um allen Regeln des Software-Designs zu folgen, haben Sie

sämtlichen Quellcode, der für die Authentifizierung verwendet wird, in einer eigenen

Klasse gekapselt. Nun soll für eine neue Anwendung der Benutzerkreis erweitert werden,

und Sie möchten auch Ihren Kunden Zugriff erlauben. Kundendaten werden allerdings

nicht im LDAP-Server, sondern in einer Datenbank gespeichert, und somit muss auch die

Authentifizierung gegen diese Datenbank erfolgen. Sie möchten also die eingegebenen

Daten zuerst im LDAP-Server suchen und, wenn Sie dort keinen gültigen Benutzer gefun-

den haben, anhand der Datenbank entscheiden, ob es sich dabei um die Zugangsdaten

eines Kunden handelt. Auch dieses Problem lässt sich leicht über das Kompositum-Pat-

tern lösen, da dieses beide Authentifizierungsobjekte zu einem Baum zusammenführen

kann, gegen den Sie dann die Authentifizierung durchführen können.

Abbildung 5-3: UML-Diagramm des Composite-Patterns

2 http://pear.php.net/package/Log

+methodA()+methodB()

Component

+methodA()+methodB()

ConcreteComponent

+addChild(child : Component)+removeChild(child : Component)+methodA()+methodB()

Composite

Debugger-Schnittstelle mitMethode debug()

Konkrete Debugger-Implementierungen

DebuggerComposite-Klasse debug()-Methode delegiertAufruf an die anderen Debugger

DebuggerComposite kannbeliebig viele Debuggerspeichern

!!"""#$#"%&'()*+,,-../0120.)3)..45012678.9*.:6;<65.)!!9..33='&.33

Adapter-Pattern

• Das Adapter-Pattern passt die Schnittstelle eines Objekts an die vom Client erwartete Schnittstelle.

• Es erleichtert die Nutzung von Fremdcode in eigenen Systemen.

• Das Adapter-Pattern arbeitet ähnlich wie ein Adapter für Steckdosen.

Adapter-Pattern

public class Automobile { private boolean ignited; private float milesDriven; public void drive(Direction direction, float miles) throws AutomobileException {

if (ignited) { milesDriven += miles; } else { throw new AutomobileException("ZŸndung ist nicht an."); } }

...}

Adapter-Pattern

public class AutomobileAdapter implements Vehicle { protected Automobile automobile; public AutomobileAdapter(Automobile automobile) { this.automobile = automobile; } public boolean moveForward(float miles) { try { automobile.drive(Direction.FORWARD, miles); return true; } catch (AutomobileException e) { return false; } }

...}

Adapter-Pattern

Das Adapter-Muster passt die Schnittstelle einer Klasse an die vom Client erwartete Schnittstelle an. Es ermöglicht die Zusammenarbeit von Klassen, die eigentlich aufgrund inkompatibler Schnittstellen nicht zusammenarbeiten können.

Adapter-Pattern

220 | Kapitel 5: Strukturmuster

Definition des Patterns

Das Adapter-Muster passt die Schnittstelle einer Klasse an die vom Client erwartete Schnitt-

stelle an. Es ermöglicht die Zusammenarbeit von Klassen, die eigentlich aufgrund inkompa-

tibler Schnittstellen nicht zusammenarbeiten können.

Zur Verwendung des Adapters, um zwischen zwei inkompatiblen Schnittstellen zu ver-

mitteln, sind die folgenden Schritte nötig:

1. Lokalisieren Sie die Unterschiede zwischen der angebotenen und der geforderten

Schnittstelle.

2. Implementieren Sie eine neue Klasse, die die geforderte Schnittstelle bereitstellt.

3. Schaffen Sie eine Möglichkeit, das zu adaptierende Objekt an den Adapter zu über-

geben, verwenden Sie dazu zum Beispiel Dependency Injection.

4. Implementieren Sie alle von der Schnittstelle geforderten Methoden und delegieren

Sie die Anfragen an die entsprechenden Methoden des Ursprungsobjekts weiter.

5. Beachten Sie Unterschiede beim Signalisieren von Fehlern.

6. Verwenden Sie in Ihrer Applikation das Adapter-Objekt, um das Ursprungsobjekt zu

ummanteln.

Wenn Sie diese einfachen Schritte befolgen, adaptieren Sie leicht die verschiedensten

Schnittstellen in Ihrer Applikation. Abbildung 5-4 zeigt Ihnen noch einmal die am Adap-

ter-Pattern beteiligten Klassen und Interfaces und wie das Pattern auf das Problem der

abweichenden Schnittstelle der Automobile-Klasse angewandt wurde.

Abbildung 5-4: UML-Diagramm des Adapter-Patterns

+methodA()

+methodA()

«interface»Target

Client

Klassen, die das Vehicle-Interface nutzen, z.B.RentalCompany

Vehicle-Interface

Automobile-Klasse

AutomobileAdapterAdapter ruft die entsprechendenMethoden auf dem Automobile-Objekt auf

Adapter

+methodB()

Adaptee

!!"""#$#"%&'()*+,,-../0120.))!..34012567.8*.95:;54.)!!8..<<='&.<<

Verhaltensmuster

• Verhaltensmuster beschreiben die Interaktion zwischen Objekten.

• Zu den Verhaltensmuster gehören unter anderem

• Subject/Observer-Pattern

• Template-Method-Pattern

• Command-Pattern

• Iterator-Pattern

Template-Method-Pattern

• Definiert die Schritte eines Algorithmus in einer Methode

• Implementierung der einzelnen Schritte bleibt Unterklassen vorbehalten

• Gemeinsames Verhalten muss nur einmal implementiert werden: Änderungen am Algorithmus nur an einer Stelle notwendig

• Neue Unterklassen müssen nur die konkreten Schritte implementieren

Template-Method-Pattern

public abstract class AbstractCar implements Vehicle {

public final void inspect() { System.out.println("FŸhre Inspektion durch"); replaceSparkPlugs(); checkTires(); if (isOilLevelLow()) { refillOil(); } } protected abstract void replaceSparkPlugs(); protected abstract void checkTires(); protected abstract boolean isOilLevelLow(); protected void refillOil() { System.out.println("FŸlle " + (300 - oilLevel) + "ml …l nach."); oilLevel = 300; }

...}

Template-Method-Pattern

public class Car extends AbstractCar {

protected void replaceSparkPlugs() { System.out.println("Ersetze Zündkerzen durch Modell AF34."); }

protected void checkTires() { System.out.println("Überprüfe Reifendruck, muss 2,0 bar sein."); }

protected boolean isOilLevelLow() { return oilLevel < 200; }

}

Template-Method-Pattern

Das Template-Method-Pattern definiert die Schritte eines Algorithmus in einer Methode und überlässt die Implementierung der einzelnen Schritte den Unterklassen. Diese können somit Teile des Algorithmus modifizieren, ohne dessen Struktur zu verändern.

Template-Method-Pattern

282 | Kapitel 6: Verhaltensmuster

Schablonenmethoden ermöglichen es, gemeinsamen Code herauszufaktorieren und

somit gemeinsames Verhalten nur einmal implementieren zu müssen.

Weitere Anwendungen

Eine weitere Anwendung der Schablonenmethode haben Sie bereits in Kapitel 3 beim

Einsatz des Factory-Method-Patterns kennengelernt. Dabei wird in einer Unterklasse der

Fabrik entschieden, wie ein Objekt erzeugt werden muss. Tatsächlich wird die Schablo-

nenmethode sehr häufig eingesetzt, wenn eine Fabrikmethode implementiert wird.

Eine abgewandelte Schablonenmethode bringt PHP schon mit. Mit der Funktion sort()

ermöglicht Ihnen PHP, ein Array mit skalaren Werten nach der Größe zu sortieren.

Sehen Sie sich dazu das folgende Beispiel an:

$ints = array(4053, 23, 283, 20032);

sort($ints);

foreach($ints as $int) {

print "{$int}\n";

}

PHP verwendet dazu einen Sortieralgorithmus, bei dem zwei nebeneinander liegende

Werte miteinander verglichen und, falls der zweite Wert kleiner ist als der erste, ver-

tauscht werden. Dies wird so lange wiederholt, bis sich die Werte in der richtigen Rei-

henfolge befinden.

Abbildung 6-2: UML-Diagramm des Template-Method-Patterns

#primitiveOperation()

ConcreteClass

+templateMethod()#primitiveOperation()

AbstractCar-Klasse

inspect()-Methode, ruft dieabstrakten Methoden auf,die in Unterklassen implementiertwerden

Car- und Convertible-Klassen

Implementieren checkTiresreplaceSparkPlugs etc.

AbstractClass

!!"""#$#"%&'()*+,,-../0120.)%)..34012567.8*.95:;54.)!!8..<<='&.<<

Command-Pattern

• Kapselt einen Auftrag als Objekt.

• Aufträge (Objekte) sind parametrisierbar

• Aufträge können in einer Queue nacheinander abgearbeitet werden

• Aufträge können rückgängig gemacht werden.

Command-Pattern

public interface CarWashCommand { void execute(Car car); }

public class CarSimpleWashCommand implements CarWashCommand { public void execute(Car car) { System.out.println("Das Auto wird gewaschen"); }}

public class CarDryingCommand implements CarWashCommand { public void execute(Car car) { System.out.println("Das Auto wird getrocknet"); }}

Command-Pattern

public class CarWash {

protected Map<String, CarWashCommand[]> programs = new HashMap<String, CarWashCommand[]>(); public void addProgram(String name, CarWashCommand... commands) { programs.put(name, commands); } public void wash(String program, Car car) { for (CarWashCommand command : programs.get(program)) { command.execute(car); } }

}

Command-Pattern

CarWash wash = new CarWash();

wash.addProgram("standard", new CarSimpleWashCommand(), new CarDryingCommand());

wash.addProgram("komfort", new CarSimpleWashCommand(), new CarEngineWashCommand(), new CarDryingCommand(), new CarWaxingCommand());

Car bmw = new Car("BMW", "silber");wash.wash("standard", bmw);

Command-Pattern

Das Command-Pattern kapselt einen Auftrag als Objekt. Dadurch wird ermöglicht, andere Objekte mit Aufträgen zu parametrisieren, Aufträge in eine Queue zu stellen oder diese rückgängig zu machen.

Command-Pattern

290 | Kapitel 6: Verhaltensmuster

Definition des Patterns

Das Command-Pattern kapselt einen Auftrag als Objekt. Dadurch wird ermöglicht, andere

Objekte mit Aufträgen zu parametrisieren, Aufträge in eine Queue zu stellen oder diese

rückgängig zu machen.

Um das Command-Pattern zu implementieren, sind also die folgenden Schritte nötig:

1. Definieren Sie eine Schnittstelle für die einzelnen Befehle.

2. Implementieren Sie die konkreten Befehle, die diese Schnittstelle bereitstellen und

die einzelnen Aufträge in einer Klasse kapseln.

3. Schaffen Sie eine Möglichkeit, den Client mit einem oder mehreren dieser Befehle zu

parametrisieren.

Mit Client ist im aktuellen Beispiel die Waschanlage gemeint, also das Objekt, das die

Befehle verwendet. Dabei müssen die Befehle nicht immer in eine Warteschlange gestellt

werden. Bei anderen Anwendungen des Command-Patterns ist es auch denkbar, dass nur

ein Befehl übergeben wird, der bei Eintreten einer bestimmten Bedingung ausgeführt

wird. Es handelt sich trotzdem um ein Command-Pattern, da der Auftrag in einer Klasse

gekapselt wird und somit zur Laufzeit ausgetauscht werden kann. Abbildung 6-3 zeigt

Ihnen die im Command-Pattern beteiligten Akteure und wie diese miteinander in Verbin-

dung stehen.

Abbildung 6-3: UML-Diagramm des Command-Patterns

-state

+execute()+action()

CarWash, alsodie Waschanlage

Car-Objekte, die gewaschenwerden

Konkrete Implementierungender Wasch-Befehle, z.B.CarMotorWashCommand

CarWashCommand-Interface

+execute()

«interface»Command

Receiver

InvokerClient

ConcreteCommand

!!"""#$#"%&'()*+,,-../0120.)3!..45012678.3*.96:;65.)!!3..<<='&.<<

Enterprise Patterns

• Kommen aus der Java Welt

• Stark geprägt durch Martin Fowler

• Meistens komplexer als die Gang-of-Four Patterns

• Deswegen ab hier keine Beispiele mehr

• Mehr zu Enterprise Patterns in PHP im Buch "PHP Design Patterns"http://www.phpdesignpatterns.de

Nutze bestehende Lösungen.Abstrahiere Deine konkreten Probleme und wende Design Patterns als Vorlage für Deine Lösung an.

Die Weisheit der Biene

Die Bulldogge

Perfektionismus wird P-A-R-A-L-Y-S-E buchstabiert

You Ain‘t Gonna Need It (YAGNI)

„Always implement things when you actually need them, never when you just foresee that you need them“

Ronald E Jeffries

You Ain‘t Gonna Need It (YAGNI)

• Anforderungen sind in der Software-Entwicklung notorisch ungenau oder wechselnd

• Ungenaue Anforderungen werden oft durch möglichst flexible und funktionsfähige Software kompensiert

• Es werden Features entwickelt die keine Anwendung finden

• Dinge die niemand braucht, haben keinen Wert.

Do the simplest thing that could possibly work.

• YAGNI kann als Ergänzung des XP-Prinzips "Do the simplest thing that could possibly work." verstanden werden

• Wann ist ein Design am einfachsten?

• Es verkörpert die Absicht des Entwicklers und besteht alle Tests.

• Es enthält kein Duplizierungen.

• Es enthält das Minimum an Klassen und Methoden

Emergenz

„Emergenz ist die spontane Herausbildung von komplexen Systemen und Strukturen durch eine Vielzahl von relativ einfachen Interaktionen.“

Wikipedia

Saubere Software durch emergentes Design

• Alle Tests bestehen

• Software muss in erster Linie den gewollten Zweck erfüllen.

• Software, die nicht testbar ist, kann nicht verifiziert werden.

• Klassen die dem Single Responsibility Prinzip folgen (auf den Tipp des Krokodils hören) sind leichter zu testen.

• Je mehr Tests wir schreiben, desto mehr bemühen wir uns Code zu schreiben, der einfacher zu testen ist.

Saubere Software durch emergentes Design

• Alle Tests bestehen

• Eine starke Kopplung erschwert das Schreiben von Tests

• Je mehr Tests wir schreiben, desto mehr bemühen wir uns, die Kopplung zu minimieren

Saubere Software durch emergentes Design

• Refactoring nach dem Bestehen eines Tests

• Duplizierten Code eliminieren

• Ausdrucksstärke des Codes verbessern

• Anzahl der Klassen und Methoden minimieren

• Tests sichern das bisherige Ergebnis ab

Vorsicht: Perfekt ist der Feind von "Gut genug"

• Entwickler tendieren dazu Lösungen danach zu analysieren wie elegant und optimal sie für die Problemstellung sind.

• Software Entwicklung ist kein Schönheitswettbewerb

• Der Code ist klar, ausdrucksstark, gut dokumentiert und getestet. Geht es noch besser?

• Klar. Aber es er ist gut genug.

• Verschwende keine Zeit auf der Suche nach dem perfekten Design

Permature Optimization

„Premature optimization is the root of all evil“Donald Knuth

Permature Optimization

• Premature Optimization beschreibt die Situation in der Design Entscheidungen aufgrund von Performance-Optimierungen getroffen werden

• Solche Optimierungen resultieren oft in unleserlicherem Code

• Michael A. Jackson über Optimierung

• Dont't do it.

• (For experts only) - Don't do it yet

Premature Optimization

• Was wenn Performance-Optimierung unumgänglich ist?

• Anwendung & Design entwickeln

• Profiler / Benchmarks einsetzen

• Flaschenhälse identifizieren

• Ein einfaches und elegantes Design ist oft leichter zu optimieren

Mit kleinen Schritten zum großen Ziel.Mach es nicht perfekt, mach es gut genug.

Je länger Entscheidungen aufgeschoben werden,

desto mehr Wissen hat man darüber

Die Weisheit der Bulldogge

Der Regenwurm

Vermeide Accidential Complexity.

Vermeide Accidential Complexity

„Simplify essential complexity; diminish accidental complexity“

Neal Ford

Essential Complexity

• "Essential Complexity" ist die Komplexität, die dem eigentlichen Problem innewohnt

• Am besten mit "notwendige Komplexität" übersetzt

• Die Komplexität ist durch das Business getrieben und kann nicht ignoriert werden

Accidential Complexity

• "Accidential Complexity" ist die Komplexität. die durch die technische Lösung hinzugefügt wird, mit der Absicht, die notwendige Komplexität zu kontrollieren

• Häufiges Problem von eingekauften Lösungen und generischen Frameworks

• Entwickler werden von Komplexität angezogen

• Entwickler wollen komplexe Probleme lösen und lösen damit oft Probleme, die die Lösung erst eingeführt hat

Accidential Complexity

• JPA

• Rules Engine

• Aspect Oriented Programming

• komplexes Build System

• Stateful Webservices

Vermeide Accidential Complexity

• Verwende Frameworks, die aus produktivem Code entstanden sind.

• Extrahiere Frameworks aus bestehendem Code.

• Prüfe, wie viel Prozent des Codes das tatsächlich vorhandene Problem adressiert

• Triff keine Entscheidungen aus dem Elfenbeinturm.

Fokussiere Dich auf das Problem.Implementiere Lösungen, die Probleme der Domäne lösen, ohne unnötige Komplexität einzuführen.

Die Weisheit des Regenwurms

Der Kugelfisch

Teste, teste, teste.

Unit Tests

„Im Unit Test werden kleinere Programmteile in Isolation von anderen Programmteilen getestet.“

Frank Westphal

Unit Tests

• Testen eine einzelne Codeeinheit isoliert.

• Die Granularität der Codeeinheit kann von Methoden über Klassen bis hin zu Komponenten reichen.

• Unit Test-Frameworks

• JUnit

• TestNG

Unit Tests

public class RentalCompanyTest extends TestCase { private RentalCompany rentalCompany; public void setUp() { rentalCompany = new RentalCompany(); } public void testAddToFleet() { rentalCompany.addToFleet("vw", new Car("VW", "silber")); int carCount = rentalCompany.countCarsInFleet(); assertEquals(1, carCount); }

}

Unit Tests

$ mvn test...------------------------------------------------------- T E S T S-------------------------------------------------------Running net.schst.tiercode.RentalCompanyTestTests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.039 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESSFUL[INFO] ------------------------------------------------------------------------

Integrationstests

„There is a vast gulf between the process mappers who model business systems pictorially, and the programmers who grind out the C++ or Java or .Net services that support those business systems. Between the two camps lies a fertile land of opportunity. It's time to jointly explore it.“

Ward Cunningham

FIT - Framework for Integrated Tests

• Framework für Integrations- und Akzeptanz-Tests

• Der Kunde schreibt die Testfälle in HTML, Word oder Excel

• Bezieht den Kunden in den Entwicklungsprozess ein und fördert agiles Vorgehen

• Mittlerweile für sehr viele Sprachen verfügbar, auch für Java

Funktionsweise

• Kunde erstellt eine Tabelle mit Eingabe-Parametern und erwarteten Rückgabe-Werten

• FIT parst die HTML-Tabelle und interpretiert diese als Testfälle

• FIT reicht die Eingabeparameter an den Testcode (Fixture) weiter

• Der Testcode führt den zu testenden Code aus

• FIT holt die das Ergebnis aus dem Testcode

• FIT markiert Abweichungen in der HTML-Tabelle

Funktionsweise

Webtests mit Selenium

• Dem Kunden sind Unit-Tests egal, wenn die Website nicht funktioniert

• Selenium prüft die Anwendung auf der Ebene, die der Kunde sieht

• Steuert den Browser fern und prüft gegen das erwartete Verhalten

• Funktioniert in allen großen Browsern, auch im IE6

• Tests können über Firefox-Extension aufgezeichnet werden

• Selenium RC ermöglicht Fernsteuerung aus JUnit Tests

Selenium in Bildern

Selenium in Bildern

Continuous Integration

• Beschreibt den Prozess des regelmäßigen, vollständigen Builds und Testens einer Anwendung

• Vorteile durch den Einsatz von CI

• Integrations-Probleme werden frühzeitig entdeckt

• Fehler werden nicht verschleppt

• Code Qualität ist über den gesamten Entwicklungsprozess sichtbar

Continuous Integration

SVN Repository CI Server

Hudson

Hudson @ 1&1

Mehr Tools

Better safe than sorry.Schaffe dir Sicherheitsnetze durch automatisierte Tests auf verschiedenen Ebenen deiner Applikation.

Integriere regelmäßig und stelle Fehlerfrühzeitig fest.

Die Weisheit des Kugelfischs

Der Waschbär

Halte deinen Code sauber.

Coding Standards

• Legen fest, wie lang eine Zeile sein darf, wie eingerückt wird, wo geklammert wird, wo Leerzeichen stehen, wann Großbuchstaben oder Kleinbuchstaben verwendet werden, wie eine Funktionsdefinition aussehen soll, wie eine Klassendefinition aussehen soll, wie eine Methodendefinition aussehen soll, wie und wo Includes verwendet werden sollen, und, und, und, …

• Haben religiöse Sprengkraft

• Sorgen dafür, dass der Code für alle Entwickler lesbar bleibt

• Entwickler haben dadurch Zeit, sich auf das Wesentliche zu fokussieren

• Checkstyle kann Code gegen verschiedene Regeln prüfen

• Liefert bereits die Regelsets von Sun mit, ist jedoch erweiterbar (hört auf die Schildkröte)

Coding Standards in Java

Verwende Name die ihre Absicht aufdecken

• Namen von Variablen, Methoden oder Klassen, sollten folgende Fragen beantworten:

• Warum existiert die Variable (Methode oder Klasse)?

• Was macht sie?

• Wie wird sie verwendet?

• int d = 1; // elapsed time in daysint elapsedTimeInDays = 1;

Benennung

• Verwende aussprechbare Namen

• Verwende ein Wort pro Konzept (z.B. fetch, retrieve oder get)

• Vermeide "Nerd Names" in Klassennamen

• ...Helper, ...Manager oder ...Util

• http://www.classnamer.com/

Benennung

Kommentare

• Gute Kommentare

• @todo-Kommentare

• Informative Kommentare

• Javadocs in öffentlichen APIs

• Schlechte Kommentare

• Redundante Kommentare

• Postionsmarkierungen

• Auskommentierter Code

Kommentare sind keine Ausrede für schlechten Code.

DRY - Don‘t Repeat Yourself

„Every piece of knowlege must have a single, unambiguous, authoritative representation within a system“

Andrew Hunt and Dave Thomas

DRY - Don‘t Repeat Yourself

• Nichts ist einfacher als Copy & Paste

• „Nummer Eins der Gestanksparade“ in Refactoring von Martin Fowler

• Jede Doppelung von Code leistet Inkonsistenzen und Fehlern Vorschub.

Fehlerhandling

• Verwende Exceptions

• Reichere deine Exceptions mit sinnvollen Informationen an.

• Definieren Exception-Klassen nach den Bedürfnissen der aufrufenden Systeme.

• Verwende Exceptions nicht als billige Alternative für goto.

• Gib niemals null zurück.

Die Pfadfinder-Regel

„Leave the campground cleaner than you found it“

Robert C. Martin

Die Pfadfinder-Regel

Die Pfadfinder-Regel

• „Don‘t live with Broken Windows“

• Fixe schlechte Designs, falsche Entscheidungen und schlechten Code sobald du ihn siehst

• Es muss nichts großes sein

• Ändere den Namen einer Variable in einen besserenBreche eine Funktion auf, die zu groß istEliminiere ein kleine Duplizierung

• Software muss dauerhaft sauber gehalten werden

Dreckiger Code führt zu dreckigem Design.Lass auch bei Detail-Fragen die gleiche Sorgfalt walten wie bei großen Design-Entscheidungen.

Die Weisheit des Waschbärs

Die Ente

Das Ende.

Die Leseratte

Fragen?

Vielen Dank für die Aufmerksamkeit.

Bildquellen © iStockphoto.com

Kontaktholger.rueprich@1und1.destephan.schmidt@1und1.de