Komponenten-basierte Entwicklung Teil 10: Dependency...
Transcript of Komponenten-basierte Entwicklung Teil 10: Dependency...
28.11.14 1Komponenten – WS 2014/15 – Teil 10/Dependency
Komponenten-basierte Entwicklung
Teil 10: Dependency Injection
Komponenten – WS 2014/15 – Teil 10/Dependency 2
Literatur
[10-1] http://de.wikipedia.org/wiki/Dependency_Injection
[10-2] http://yan.codehaus.org/Dependency+Injection+Types
[10-3] http://martinfowler.com/articles/injection.html
[10-4] http://de.wikipedia.org/wiki/Liste_von_Dependency_Injection_Frameworks
[10-5] http://stackoverflow.com/questions/557742/dependency-injection-vs-factory-pattern
Komponenten – WS 2014/15 – Teil 10/Dependency 3
Übersicht
• Zusammenbau von Hand
• Fabrik
• Injection über den Konstruktor
• Injection mit Hilfe von set()-Methoden
• Injection über Annotationen
Komponenten – WS 2014/15 – Teil 10/Dependency 4
Abhängigkeiten - Wiederholung
• Abhängigkeit = Dependency = Gerichtete Relation zwischen zwei Codestücken A und B, wobei die Korrektheit von A von der Korrektheit von B abhängt, in dem Sinne, dass A fehlerhaft ist, wenn B fehlerhaft ist. Dann hängt A von B ab.
• Abhängigkeiten können wie folgt realisiert werden:– A ruft B auf (z.B. Call, Methodenaufruf)
– B produziert Daten für A (z.B. Daten, Messwerte)
– B ist der Manager für A (z.B. Starten, Stoppen, Scheduling)
– A erbt von B
– B interpretiert die Software von A (z.B. CPU, Virtuelle Maschine)
– B übersetzt die Software von A (z.B. Compiler, Assembler)
• Wir beschäftigen uns hier nur mit den Aufruf-Abhängigkeiten.
Komponenten – WS 2014/15 – Teil 10/Dependency 5
Klasse Oponent – Wiederholung I
public class Oponent implements Moves { private int humanTurn; private int myTurn; private MyChoice dice; Oponent() { humanTurn= -1; myTurn = -1; dice= new MyChoice(); }… … …
FesteVerdrahtung
Das Ziel dieser Einheit besteht darin, den Umgang mit Abhängigkeitenzu vereinfachen – beseitigt werden diese nicht.
Komponenten – WS 2014/15 – Teil 10/Dependency 6
Ein Beispiel – Event Manager
Location DescriptionEventAppointment
• Location: Liste der Orte
• Event: Liste der Aktivitäten
• Description: Beschreibung der Aktivität
• Appointment: Zusammenfassung samt Datum und Uhrzeit
Siehe: http://de.wikipedia.org/wiki/Martin-Notation
Komponenten – WS 2014/15 – Teil 10/Dependency 7
Event Objekte zu Fuß I
public class Location { String street; String houseNumber; String city; String postalCode; Location(String street,String houseNumber,String city,
String postalCode) { this.street= street; this.houseNumber= houseNumber; this.city= city; this.postalCode= postalCode; } private Location() {}
@Override public String toString() { return street+" "+houseNumber+" "+postalCode+" "+city; }}
Datenmodellierung
Initialisierung
Verbot
Komponenten – WS 2014/15 – Teil 10/Dependency 8
Event Objekte zu Fuß II
public class Event { EventType mode; Description description; Event(EventType mode, Description description) { this.mode= mode; this.description= description; } private Event() {}
@Override public String toString() { return description.toString(); }}
public enum EventType { BIRTHDAY, BOOZY_PARTY, DANCE_PARTY, GIG, FUNERAL_SERVICE;}
Datenmodellierung
Komponenten – WS 2014/15 – Teil 10/Dependency 9
Event Objekte zu Fuß III
public class Description { String text; Description(String text) { this.text= text; } private Description() {} @Override public String toString() { return text; }}
Datenmodellierung
Komponenten – WS 2014/15 – Teil 10/Dependency 10
Event Objekte zu Fuß IV
public class Appointment { String date; String time; Location location; Event event; Appointment(String date,String time,Location location,Event event) { this.date= date; this.time= time; this.location= location; this.event= event; } private Appointment() {}
@Override public String toString() { return date+" "+time; }}
Datenmodellierung
Komponenten – WS 2014/15 – Teil 10/Dependency 11
Event Objekte zu Fuß V
public class EventManager { Appointment[] apps; EventManager() { apps= new Appointment[10]; init(); } private void init() { Location loc= new Location("Müllerstr.","12a","Berlin","11089"); Description des=new Description("Geburtstag des großen Vorsitzenden"); Event eve= new Event(EventType.BIRTHDAY, des); apps[0]= new Appointment("10. Mai 2012","19:00",loc,eve); … … … } protected void printAppointments() { for(Appointment app : apps) { if(app!=null) { System.out.printf("Am %s: %s Ort %s ist %s\n", app,app.event.mode,app.location,app.event.description); } } }}
Zusammenbau
Zugriff
Komponenten – WS 2014/15 – Teil 10/Dependency 12
Event Objekte zu Fuß VI
public class App { public static void main( String[] args ) { System.out.println("Liste der Feten 2012"); EventManager ev= new EventManager(); ev.printAppointments(); }}
Liste der Feten 2012Am 10. Mai 2012 19:00: BIRTHDAY Ort Müllerstr. 12A … ist Geburtstag des großen VorsitzendenAm 1. April 2012 18:00: BOOZY_PARTY Ort Karl-May-Allee … ist Gelage bei WilliAm 20. November 2012 17:00: DANCE_PARTY … ist Square Dance ab 20Am 24. Dezember 2012 22:00: GIG Ort … ist Neues Schlagzeug einweihen
„Hauptprogramm“
Output
Komponenten – WS 2014/15 – Teil 10/Dependency 13
Bemerkungen
• Vorteile– Einfach zu verstehen
– Beziehungen im Datenmodell werden zu Referenzen
• Nachteile– überhaupt nicht änderungsfreundlich
– Referenzen hart verknotet im Code
Komponenten – WS 2014/15 – Teil 10/Dependency 14
Thema: Getter/Setter I – Direkter Zugriff
public class EventManager { … … … private void init() { … … … loc= new Location("","","",""); loc.city= "Berlin"; loc.street= "Gustav-Meyring-Ring"; loc.houseNumber= "2 Keller"; loc.postalCode= "??"; des= new Description("Neue Gitarre einweihen"); eve= new Event(EventType.GIG, des); apps[4]= new Appointment("25. Dezember 2012","11:00",loc,eve); } … … … if(app!=null) { System.out.printf("Am %s: %s Ort %s ist %s\n", app,app.event.mode,app.location,app.event.description);
Direkter Zugriff auf die Attribute eines Objekts.
Komponenten – WS 2014/15 – Teil 10/Dependency 15
Thema: Getter/Setter II – Zugriff über Funktionen
public class EventManager { … … … private void init() { … … … loc= new Location("","","",""); loc.setCity("Berlin"); loc.setStreet("Gustav-Meyring-Ring"); loc.setHouseNumber("2 Keller"); loc.setPostalCode("??"); des= new Description("Neue Gitarre einweihen"); eve= new Event(EventType.GIG, des); apps[4]= new Appointment("25. Dezember 2012","11:00",loc,eve); } … … … System.out.printf("Am %s: %s Ort %s ist %s\n", app, app.getEvent().getMode(), app.getLocation(), app.getEvent().getDescription();
Die Attribute sind nun alle private.
Komponenten – WS 2014/15 – Teil 10/Dependency 16
Thema: Getter/Setter III – Bemerkungen
• Nachteile– Code wird erheblich länger und ist schwerer zu überschauen.
– Zugriffsmethoden haben keine/kaum Semantik.
• Vorteile– Es können leicht Prüfungen eingeführt werden, ohne das der Aufrufer
davon etwas merkt.
– Aufgrund der Abstraktion kann die Implementierung auch ohne Attribute erfolgen, z.B. durch Delegation oder Berechnung.
– Es können „Lese/Schreibrechte“ vergeben werden.
– Methoden können mit Mocks simuliert werden.
Warum mit get/set-Methoden auf die Attribute zugreifen?
C# hat diese sinnfreien get/set-Methodenlisten besser im Griff.
Komponenten – WS 2014/15 – Teil 10/Dependency 17
Event Objekte mit Fabrik I
AbstractFactory
factoryMethod()
Factory
factoryMethod()
extends
AbstractProduct
Methods...()
Product
extends
createsMethods...()
Consumer
Methods...()
callsuse
Factory-Pattern
Komponenten – WS 2014/15 – Teil 10/Dependency 18
Event Objekte mit Fabrik II
FactoryInterface
createObject()
Factory
implements
AbstractProduct
Methods...()
Product
extends
creates
Consumer
Methods...()
calls
calls
Static(!)
Initialisiert
newObject() createObject()
Komponenten – WS 2014/15 – Teil 10/Dependency 19
Event Objekte mit Fabrik III
public interface AppointmentCreation { Appointment createAppointment();}
public class AppointmentFactory implements AppointmentCreation { @Override public Appointment createAppointment() { Location loc= new Location(); Description des= new Description(); Event eve= new Event(des); return new Appointment(loc,eve); }}
Komponenten – WS 2014/15 – Teil 10/Dependency 20
Event Objekte mit Fabrik IV
public abstract class AbstractAppointment { private String date; private String time; private Location location; private Event event; AbstractAppointment(String date,String time, Location location,Event event) { … … … } AbstractAppointment(Location location,Event event) { … … … } public void setStreet(String street) { location.street= street; } public String getStreet() { return location.street; } … … …
get()/set() mitallen Attributen
Komponenten – WS 2014/15 – Teil 10/Dependency 21
Bemerkungen
• Nun wird die Klasse mit allen get()/set()-Methoden versehen.
• Es werden alle(!) Attribute über die Klasse Appointment zugänglich gemacht, d.h.die interne Struktur wird versteckt.
• Die Klasse wird dadurch recht lang.
Komponenten – WS 2014/15 – Teil 10/Dependency 22
Event Objekte mit Fabrik V
public class Appointment extends AbstractAppointment {
Appointment(String date, String time, … … … ) { super(date, time, location, event); } Appointment(Location location, Event event) { super(location, event); } public static Appointment newObject(int record) { Appointment app; AppointmentCreation factory= new AppointmentFactory(); app= factory.createAppointment(); switch (record) { case 0: app.setStreet("Müllerstr."); app.setHouseNumber("12a"); … … … app.setTime("19:00"); break; } return app; }}
Initialisierung
Erzeugung
Komponenten – WS 2014/15 – Teil 10/Dependency 23
Event Objekte mit Fabrik VI
public class EventManager { Appointment[] apps; EventManager() { apps= new Appointment[10]; apps[0]= Appointment.newObject(0); } protected void printAppointments() { for(Appointment app : apps) { if(app!=null) { System.out.printf("Am %s %s: %s Ort %s %s %s %s ist %s\n", app.getDate(),app.getTime(), app.getMode(),app.getStreet(), app.getHouseNumber(),app.getPostalCode(), app.getCity(), app.getDescription()); } } }
Erzeugung
Komponenten – WS 2014/15 – Teil 10/Dependency 24
Bemerkungen I
• Einzig der Aufruf newObject() ist alles zum Erzeugen eines Objekt-Geflechtes.
• Die Zugriffsroutinen getXXX() sorgen genauso für Abstraktion.
• Das Ziel war es durch Abstraktion eine Trennung zwischen den benutzenden Klassen und den erzeugenden Klassen zu schaffen.
• Dies führt zu zwar längeren, aber besser zu wartenden Code.
Komponenten – WS 2014/15 – Teil 10/Dependency 25
Bemerkungen II
• Es sind verschiedene Fabriken möglich, z.B. für Mocks.
• Aber: Die Wahl der Fabrik ist in newObject() fest kodiert.
FactoryInterface
createObject()
implements
AbstractProduct
Methods...()
Product
extends
creates
calls
newObject()
Factory
createObject()
Factory
createObject()
Factory
createObject()
VerschiedeneFabriken möglich
Komponenten – WS 2014/15 – Teil 10/Dependency 26
Bemerkungen III
public static Appointment newObject(int record) { Appointment app; AppointmentCreation factory= new AppointmentFactory(); app= factory.createAppointment(); … … … Initialisierung … … … return app;}
Fest im Code
Das Zusammenbauen von Objektgeflechten ist zwar versteckt,aber hart kodierte Abhängigkeiten bestehen wie bei der erstenVariante.
Komponenten – WS 2014/15 – Teil 10/Dependency 27
Klasse Oponent – Wiederholung II
• Die Benutzung einer anderen Klasse wird Dependency (Abhängigkeit) genannt.
• Wenn die Abhängigkeit von Außen gesteuert werden kann, wird dies Dependency Injection genannt.
• Hier erfolgt dies mit Hilfe des Konstruktors.
@Before public void setUp() { opo= new Oponent(new MyChoice());}
private DiceInterface dice;
Oponent(DiceInterface dice) { humanTurn= -1; myTurn = -1; this.dice= dice;}
Änderung in derKlasse Oponent(Konstruktor)
Änderung in derKlasse OponentTest(setUp())
Komponenten – WS 2014/15 – Teil 10/Dependency 28
DI mit Konstruktoren I
• Eine Dependency Injection durch Konstruktoren liegt dann vor, wenn als Parameter des Konstruktors eine Referenz auf die Abhängigkeit übergeben wird.
• Im Konstruktor wird die Referenzvariable dann gesetzt.
• Die Entscheidung über die Abhängigkeit wird beim new() getroffen, also recht früh.
• In den Beispielen hatten wir das mehrmals.
Komponenten – WS 2014/15 – Teil 10/Dependency 29
DI mit Konstruktoren II
public abstract class AbstractAppointment { … … … private Location location; private Event event; AbstractAppointment(… … … ,Location location,Event event) { … … … this.location= location; this.event= event; } AbstractAppointment(Location location,Event event) { this.location= location; this.event= event; } … … …
DI imKonstruktor
Komponenten – WS 2014/15 – Teil 10/Dependency 30
DI mit setXXX() I
• Eine Dependency Injection durch setXXX() liegt dann vor, wenn als Parameter einer set()-Methode eine Referenz auf die Abhängigkeit übergeben wird.
• In dieser Methode wird die Referenzvariable dann gesetzt.
• Die Entscheidung über die Abhängigkeit wird nach einem new() getroffen, also spät, vielleicht sogar erst zu spätesten Zeitpunkt.
• Derartige DI hat den großen Vorteil, dass in Abhängigkeit vom Kontext nur der notwendige Teil des Objektgeflechtes aufgebaut wird.
• Dann gibt es noch eine Variante, bei der die Methode (set()) zur Injektion über ein Interface erzwungen wird. Dies ist aber eine Variante des hier vorgestellten Vorgehen.
Komponenten – WS 2014/15 – Teil 10/Dependency 31
Dritte Variante mit Annotationen I
• Wir bleiben bei der Variante der Fabrik, weil dort viel Abstraktion realisiert ist - aber die fest kodierte Wahl der Fabrik wird geändert.
@Injection static AppointmentCreation factory;
public static Appointment newObject(int record) { Appointment app= factory.createAppointment(); … … … return app;}
Es wird lediglich ein statisches Attribut nicht initialisiert.Bitte beachten Sie, dass die statische Methode newObject()ohne Injection nicht funktionstüchtig ist, da die Fabrik fehlt.
KeineVorbelegung
Komponenten – WS 2014/15 – Teil 10/Dependency 32
Dritte Variante mit Annotationen II
public class AppointmentAlternativeFactory implements AppointmentCreation { @Override public Appointment createAppointment() { System.out.println("AppointmentAlternativeFactory is used"); Location loc= new Location(); Description des= new Description(); Event eve= new Event(des); return new Appointment(loc,eve); }}
Beginnen wir mit einer anderen Factory.Beide werden durch eine Ausgabe ergänzt,machen aber dasselbe.
Komponenten – WS 2014/15 – Teil 10/Dependency 33
Dritte Variante mit Annotationen III
public static void main( String[] args ) { Injections clazzes= new Injections(); clazzes.processInjections(Appointment.class,null);
System.out.println("Liste der Feten 2012"); EventManager ev= new EventManager(); ev.printAppointments();}
public class EventManager { Appointment[] apps; EventManager() { apps= new Appointment[10]; apps[0]= Appointment.newObject(0); }… … …
Hier erfolgt derFabrik-Aufruf
Injection
Komponenten – WS 2014/15 – Teil 10/Dependency 34
Dritte Variante mit Annotationen IV - class Injections
public class Injections { private Class clazz; private Object obj; private Class toInject; Injections() { toInject= AppointmentAlternativeFactory.class; toInject= AppointmentFactory.class; } … … … public void processInjections(Class clazz, Object obj) { this.clazz= clazz; this.obj= obj; processAnnotations(clazz); }
Die beidenFabriken
public static void main( String[] args ) { Injections clazzes= new Injections(); clazzes.processInjections(Appointment.class,null); … … … }
Nur bei statischenObjekten
Komponenten – WS 2014/15 – Teil 10/Dependency 35
Dritte Variante mit Annotationen V - class Injections
private void processAnnotations(Class clazz){ for(Field attr : clazz.getDeclaredFields()) { Injection val= attr.getAnnotation(Injection.class); if(val!=null) { injectIt(attr); } }}
import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)
public @interface Injection { }
Komponenten – WS 2014/15 – Teil 10/Dependency 36
Dritte Variante mit Annotationen V - class Injections
private void injectIt(Field attr){ if(!Modifier.isStatic(attr.getModifiers())&&(obj==null)) { throw new IllegalArgumentException("dynamic needs an object not null"); } if (!attr.getType().isAssignableFrom(toInject)) { throw new IllegalArgumentException("cant inject incompatible class"); } Try { attr.setAccessible(true); attr.set(this.obj,createInstance(toInject)); } catch (IllegalAccessException ex) { System.out.println(ex); }}
Komponenten – WS 2014/15 – Teil 10/Dependency 37
Bemerkungen
if(!Modifier.isStatic(attr.getModifiers())&&(obj==null)) {
prüft, ob der Modifier static ist; denn wenn das nicht ist, dann musseine Referenz zu einem Objekt da sein.
if (!attr.getType().isAssignableFrom(toInject)) {
prüft, ob der Variablen der vorgesehene Wert zugewiesen werdenkann; ansonsten gibt es eine Exception.
attr.set(this.obj,createInstance(toInject));
weist den Wert zu; dieser ist eine Instanz, die erst noch erzeugtwerden muss. Hier ohne Konstruktor-Parameter.
Komponenten – WS 2014/15 – Teil 10/Dependency 38
Dritte Variante mit Annotationen VI - class Injections
private Object createInstance(Class clazz) { Object object= null; try { object= clazz.newInstance(); } catch (InstantiationException | IllegalAccessException ex) { System.out.println(ex); } return object;}
Hier wird anhand des Klassendeskriptors eine Instanz erzeugt,wobei der Konstruktor keinen Parameter haben darf.
Liste der Feten 2012AppointmentFactory is usedAm 10. Mai 2012 19:00: BIRTHDAY Ort ... ist Geburtstag des grossen Vorsitzenden
Output
Komponenten – WS 2014/15 – Teil 10/Dependency 39
Schlussbemerkungen
• Es wurden verschiedene Vereinfachungen vorgenommen:– Keine Spezifikation in Dateien (von außen)
– Nur eine Klasse zum Injizieren
– Nur eine Klasse wird untersucht
• Es sollte nur das Prinzip gezeigt werden.
• Das Ganze geht natürlich auch ohne Annotation, aber trotzdem mit Reflexion.