1. Die Kunst des Software-Design
oder was Software-Entwickler von der Tierwelt lernen können
1&1 Internet AG, 8. Februar 2011
Holger Rüprich und Stephan Schmidt
2. 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
3. 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
4. Software Design
„Software Design is a process of problem solving
and planning for a software solution.“
Wikipedia
5. Kunst
„Das Wort Kunst bezeichnet im weitesten Sinne
jede entwickelte Tätigkeit, die auf Wissen, Übung,
Wahrnehmung, Vorstellung und Intuition
gegründet ist.“
Wikipedia
8. Von der realen Welt ...
• Bob hat eine Autovermietung
• Er muss Autos einkaufen
• Er vermietet unterschiedliche
Modelle
• Er vermietet Autos zu verschiedenen
Preisen
9. ... 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() {}
}
10. 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;
}
}
11. 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;
}
}
12. 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;
}
}
13. 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) {}
}
14. Bobs Geschäft
• Klassen übertragen nicht nur physikalische Dinge, sondern auch Abläufe
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 ...
}
15. Bobs Geschäft
• Klassen übertragen nicht nur physikalische Dinge, sondern auch Abläufe
public class RentalAction {
...
public void markCarReturend() {
markCarReturend(new Date());
}
public void markCarReturend(Date date) {
returnDate = date;
}
public boolean isReturend() {
return returnDate != null;
}
}
16. Kapsle auch Algorithmen
Kapsle nicht nur Daten sondern auch Algorithmen
in den Methoden deiner Klassen, um komplexe
Operationen zentral an einer Stelle zu
implementieren.
17. Bobs Firma
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;
}
}
18. 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;
}
}
19. 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;
}
}
20. Die Weisheit des Eichhörnchens
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.
23. 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
24. 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);
} Was ist mit Problemen im Produktivbetrieb?
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
25. 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;
}
}
26. 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; Code Duplizierung macht die Anwendung
}
langsamer und schwerer zu warten
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;
27. 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());
}
...
}
28. 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;
}
}
}
29. Wiederverwendbarkeit statt Copy-and-Paste
public class RentalCompany {
...
Weitere Debugging-Ziele, wie E-Mails oder SMS,
private void debug(String message) {
switch (DEBUG_MODE) {
machen die debug()-Methode komplexer
case ECHO:
System.out.println(message);
break;
case LOG: und somit fehleranfälliger.
default:
logger.info(message);
break;
}
}
30. 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);
}
}
31. 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);
32. Atomare Probleme lösen
RentalCompany company;
switch (DEBUG_MODE) {
case ECHO:
company = new EchoingRentalCompany();
break;
case LOG:
Die debug()-Methode steht nur Unterklassen von
default:
company = new LoggingRentalCompany();
}
break;
RentalCompany zur Verfügung.
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);
33. 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);
}
}
34. 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());
}
...
}
35. Die Weisheit des Krokodils
Teile und herrsche.
Jedes Modul soll genau eine Verantwortung übernehmen, und jede
Verantwortung soll genau einem Modul zugeordnet werden.
37. Das Open-Closed-Prinzip
„Software entities (classes, modules, functions,
etc.) should be open for extension, but closed for
modification.“
Bertrand Meyer
38. 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());
}
...
}
39. Komposition statt Vererbung
class RentalCompany {
... Eigenschaften der Klasse ...
public RentalCompany() {
switch (DEBUG_MODE) {
case ECHO:
debugger = new DebuggerEcho();
break;
case LOG:
default: Die RentalCompany Klassen ist von
anderen Klassen abhängig
debugger = new DebuggerLog();
break;
}
}
public void addToFleet(String id, Vehicle vehicle) {
fleet.put(id, vehicle);
debugger.debug("Neues Auto im Fuhrpark: " + vehicle.getManufacturer());
}
...
}
40. 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);
41. 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.
43. Das Hollywood Prinzip
„Rufen Sie uns nicht an, wir werden Sie anrufen.“
Das Hollywood Prinzip
44. 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
45. 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
46. Dependency Injection
Debugger debugger = new DebuggerEcho();
RentalCompany company = new RentalCompany(debugger); Constructor Injection
Logger logger = new DateTimeLogger();
RentalCompany company.setLogger(logger); Setter Injection
• 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
47. 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, ...
48. Google Guice
• Basiert komplett auf Java-Code
• Keine externe Konfiguration
• Stattdessen Einsatz von Annotations
• Sehr leichtgewichtig
49. Rufen Sie Bob nicht an, Bob ruft sie an.
class RentalCompany {
... Eigenschaften der Klasse ...
@Inject
public void setLogger(Logger logger) { Setter Injection
this.logger = logger;
}
...
}
50. Interfaces binden
• 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 Objekte
RentalCompany company = injector.getInstance(RentalCompany.class);
Car bmw = new Car("BMW", "silver");
company.addToFleet("bmw", bmw);
51. Komplexeres Beispiel
• Neuer Typ „Formatter“, inkl. Implemetierungen:
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();
}
}
52. Komplexeres Beispiel
• Einsatz in DebuggerEcho:
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))
}
}
53. 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(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);
}
}
54. Komplexeres Beispiel
• Benannte Injections:
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);
}
}
55. Komplexeres Beispiel
• Und jetzt die Bindings dazu:
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);
}
}
• Und jetzt die Bindings dazu:
Injector injector = Guice.createInjector(new MyApplicationModule());
MyApplication app = injector.getInstance(MyApplication.class);
app.doSomething("Hollywood");
57. Komplexeres Beispiel
• Ausgabe des Beispiels
Sending 'doowylloH' to Debugger.
DOOWYLLOH
DebuggerEcho UppercaseFormatter
MyApplication
ReverseFormatter
58. Weitere Features von Guice
• 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.
60. Das selbe nochmal, mit Spring
• Erzeugen der Beans
ApplicationContext context = new ClassPathXmlApplicationContext("spring/spring-beans.xml");
MyApplication app = context.getBean("application", MyApplication.class);
app.doSomething("Hollywood");
• Ausgabe:
Sending 'doowylloH' to Debugger.
DOOWYLLOH
61. Dasselbe nochmal, mit XJConf
• Tag-Definitionen über XML:
<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>
• Nicht ganz dasselbe:
• XJConf ist schlecht bei Constructor-Injection. Also alles auf Setter-Injection
umgestellt.
62. Dasselbe nochmal, mit XJConf
• Definition der Objekte über XML:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<Application>
<EchoDebugger>
<UppercaseFormatter/>
</EchoDebugger>
<ReverseFormatter/>
</Application>
</configuration>
63. Dasselbe nochmal, mit XJConf
• Erzeugen der Objekte:
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");
• Ausgabe:
Sending 'doowylloH' to Debugger.
DOOWYLLOH
64. Die Weisheit des Erpel
Verwende ein DI Framework.
Erleichtere dir das Verwalten von komplexen Objektstrukturen
durch den Einsatz eines Dependency Injection Frameworks.
66. 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
67. 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
68. Konkret vs Abstrakt
Abstrakt Konkret
Verarbeiten von Debug-
Aufgabe
Meldungen
Ausgeben per print(),
Algorithmen
Schreiben eines Logfiles
Die Klasse
Client
RentalCompany
69. Konkret vs Abstrakt
Abstrakt Konkret
Persistieren von
Aufgabe
Gästebucheinträgen
Speichern in Datenbank,
Algorithmen
Speichern in XML-Datei
Client Die Klasse Guestbook
71. 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
72. 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
73. 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
74. 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();
}
75. 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();
76. 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.
77. der abstrak
lichen Objekte zu
4. Verwenden Sie nun diese konk reten Unterklassen, um die tatsäch
ementierungen zu
instanziieren und Ihren Applik ationscode von den konkreten Impl
lösen.
fach darauf, die
verwenden möchten, achten Sie ein
Factory-Method-Pattern
Wann immer Sie eine Fabrikmetho
de
hier gezeigten Schritte durchzuführe
n, und dem Erfolg Ihres Vorhabens
steht nichts mehr
teiligten des Fac-
n die Beziehungen zwischen den Be
im Weg. Abbildung 4-2 zeigt Ihne f die Erzeugung der
tory-Method-Patterns und illustri ert noch einmal, wie das Pattern au
cle-Implementierungen angewa
ndt wurde.
Vehi
sell Vehicle-Methode, ruft
Vehicle-Interface Fabrikmethode auf
Creator
Product
+FactoryMethod() : Product
+MethodA()
AbstractManufacturer- CarManufacturer und
Klasse ConvertibleManufacturer
ConcreteCreator
ConcreteProduct
+FactoryMethod() : Product
Implementierungen des Vehicle- Fabrikmethode manufactureVehicle
anzen
Interfaces wie Car, Convertible et
c. erzeugt Car- bzw. Convertible-Inst
s Factory-Method-Patterns
Abbildung 4-2: UML-Diagramm de
Das Factory-Method-Pattern | 177
78. 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
79. 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.");
}
}
80. 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");
81. 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.
82. Prototype-Pattern
!!"""#$#"%&'()*+,,-../0120.)!!..3
4012567.8*.95:;54.)!!8..<<='&.<<
Vehicle-Interface
SpecialEditionManufacturer __clone()-Methode
kennt alle verfügbaren in PHP nicht immer nötig
Prototypen
rface wie
Implementierungen des Vehicle-Inte
Car, Convertible etc.
e-Patterns
Abbildung 4-6: UML-Diagramm des Prototyp
plikation
reduziert die Anzahl der Klassen, die Ihre Ap
Der Einsatz des Prototype-Patterns ukte zu
sen bild en müssen, um die einzelnen Prod
benötigt, da Sie weniger Unterklas en von einer Klasse
erzeugen. Stattdessen werden al le Produkte auf Basis der Prototyp
83. Strukturmuster
• Strukturmuster befassen sich mit der Komposition von Objekten
• Zu den Strukturmustern gehören unter anderem
• Composite-Pattern
• Proxy-Pattern
• Adapter-Pattern
• Facade-Pattern
84. 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
85. 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);
}
}
86. 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());
87. 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.
88. Composite-Pattern
!!"""#$#"%&'()*+,,-../0120.)3)..45
012678.9*.:6;<65.)!!9..33='&.33
DebuggerComposite kann
Debugger-Schnittstelle mit beliebig viele Debugger
Methode debug() speichern
Component
+methodA()
+methodB()
Konkrete Debugger-
Implementierungen
Composite
ConcreteComponent
+addChild(child : Component)
+methodA()
+removeChild(child : Component)
+methodB()
+methodA()
+methodB()
DebuggerComposite-Klasse debug()-Methode delegiert
Aufruf an die anderen Debugger
s Composite-Patterns
Abbildung 5-3: UML-Diagramm de
Weitere Anwendungen e Anwendung des
haben Sie bereits eine sehr beliebt
Mit der Debugging-Funktionalität e ähnliche
etet das PEAR-Paket Log , das ein
2
Kompositum-Patterns kenn engelernt. So bi si-
llt, auch eine Klasse, die als Kompo
89. 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.
90. 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.");
}
}
...
}
92. 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.
93. Sie die Anf
von Fehlern.
5. Beachten Sie Unterschiede beim Signalisieren
prungsobjekt zu
ion das Adapter-Objekt, um das Urs
6. Verwenden Sie in Ihrer Applikat
ummanteln.
Adapter-Pattern
Wenn Sie diese einfachen Schritte
Ab
befolgen, adaptieren Sie leicht die
verschiedensten
bildung 5-4 zeigt Ihnen noch einmal
die am Adap-
Schnittstellen in Ihrer Applikation. oblem der
Interfa ces und wie das Pattern auf das Pr
ter-Pattern beteiligten Klassen und wandt wurde.
abweichenden Schn ittstelle der Automobile-Klasse ange
Klassen, die das Vehicle-
Interface nutzen, z.B. Vehicle-Interface
RentalCompany
Client «interface»
Target
+methodA()
Automobile-Klasse
Adaptee
Adapter
+methodB()
+methodA()
AutomobileAdapter
Adapter ruft die entsprechenden
Methoden auf dem Automobile-
Objekt auf
apter-Patterns
Abbildung 5-4: UML-Diagramm des Ad
94. Verhaltensmuster
• Verhaltensmuster beschreiben die Interaktion zwischen Objekten.
• Zu den Verhaltensmuster gehören unter anderem
• Subject/Observer-Pattern
• Template-Method-Pattern
• Command-Pattern
• Iterator-Pattern
95. 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
97. 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;
}
}
98. 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.
99. Template-Method-Pattern
!!"""#$#"%&'()*+,,-../0120.)%)..34
012567.8*.95:;54.)!!8..<<='&.<<
AbstractCar-Klasse
AbstractClass
+templateMethod()
#primitiveOperation()
inspect()-Methode, ruft die
abstrakten Methoden auf,
die in Unterklassen implementiert
werden
Car- und Convertible-Klassen
ConcreteClass
Implementieren checkTires
#primitiveOperation() replaceSparkPlugs etc.
e-Method-Patterns
Abbildung 6-2: UML-Diagramm des Templat
ktorieren und
es, gemeinsamen Code herauszufa
Schablonenmethoden ermöglichen n zu müssen.
somit gemeinsames Verhalten nur einmal implementiere
100. 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.
101. 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");
}
}
102. 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);
}
}
}
103. 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);
104. 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.
105. parametrisieren.
t, also das Objekt, das die
Mit Client ist im aktuellen Beispiel die Waschanlage gemein
Warteschlange gestellt
Befehle verwendet. Dabei müs sen die Befehle nicht immer in eine
denkbar, dass nur
des Command-Patterns ist es auch
werden. Bei anderen Anwendungen ausgeführt
i Eint reten einer bestimmten Bedingung
ein Befehl übergeben wird, der be
Command-Pattern
wird. Es handelt sich trotzdem um ein Command-Pattern, da der Au
zeit ausgetauscht werden kann. Ab
ftrag in einer Klasse
bildung 6-3 zeigt
gekapselt wird und somit zur Lauf se miteinander in Verbin-
Ihnen die im Command-Pa ttern beteiligten Akteure und wie die
dung stehen.
CarWash, also CarWashCommand-
die Waschanlage Interface
«interface»
Client Invoker
Command
+execute()
Car-Objekte, die gewaschen
werden
Receiver ConcreteCommand
-state
+execute()
+action()
Konkrete Implementierungen
der Wasch-Befehle, z.B.
CarMotorWashCommand
Command-Patterns
Abbildung 6-3: UML-Diagramm des
290 | Kapitel 6: Verhaltensmuster
106. 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
107. Die Weisheit der Biene
Nutze bestehende Lösungen.
Abstrahiere Deine konkreten Probleme und wende Design Patterns
als Vorlage für Deine Lösung an.
109. 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
110. 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.
111. 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
112. Emergenz
„Emergenz ist die spontane Herausbildung von
komplexen Systemen und Strukturen durch eine
Vielzahl von relativ einfachen Interaktionen.“
Wikipedia
113. 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.
114. 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
115. 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
116. 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
118. 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
119. 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
120. Die Weisheit der Bulldogge
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
123. 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
124. 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
126. 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.
127. Die Weisheit des Regenwurms
Fokussiere Dich auf das Problem.
Implementiere Lösungen, die Probleme der Domäne lösen, ohne
unnötige Komplexität einzuführen.
129. Unit Tests
„Im Unit Test werden kleinere Programmteile in
Isolation von anderen Programmteilen getestet.“
Frank Westphal
130. 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
131. 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);
}
}
132. Unit Tests
$ mvn test
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running net.schst.tiercode.RentalCompanyTest
Tests 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] ------------------------------------------------------------------------
133. 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
134. 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
135. 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
137. 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
140. 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
145. Die Weisheit des Kugelfischs
Better safe than sorry.
Schaffe dir Sicherheitsnetze durch automatisierte Tests auf
verschiedenen Ebenen deiner Applikation.
Integriere regelmäßig und stelle Fehler
frühzeitig fest.
147. 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
148. Coding Standards in Java
• Checkstyle kann Code gegen verschiedene Regeln prüfen
• Liefert bereits die Regelsets von Sun mit, ist jedoch erweiterbar (hört auf die
Schildkröte)
149. 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 days
int elapsedTimeInDays = 1;
150. 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/
152. Kommentare
• Gute Kommentare • Schlechte Kommentare
• @todo-Kommentare • Redundante Kommentare
• Informative Kommentare • Postionsmarkierungen
• Javadocs in öffentlichen APIs • Auskommentierter Code
Kommentare sind keine Ausrede für schlechten Code.
153. DRY - Don‘t Repeat Yourself
„Every piece of knowlege must have a single,
unambiguous, authoritative representation
within a system“
Andrew Hunt and Dave Thomas
154. 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.
155. 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.
158. 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 besseren
Breche eine Funktion auf, die zu groß ist
Eliminiere ein kleine Duplizierung
• Software muss dauerhaft sauber gehalten werden
159. Die Weisheit des Waschbärs
Dreckiger Code führt zu dreckigem Design.
Lass auch bei Detail-Fragen die gleiche Sorgfalt walten wie bei
großen Design-Entscheidungen.