Die Kunst des Software Design - Java
-
Upload
stephan-schmidt -
Category
Technology
-
view
2.681 -
download
1
description
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
[email protected]@1und1.de