Komponenten-basierte Entwicklung Teil 10: Dependency...

40
28.11.14 1 Komponenten – WS 2014/15 – Teil 10/Dependency Komponenten-basierte Entwicklung Teil 10: Dependency Injection

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

Drucken

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.

Komponenten – WS 2014/15 – Teil 10/Dependency 40

Nach dieser Anstrengung etwas Entspannung...