DIY - DI

47
DIY - DI Adam Suwała

description

Adam Suwała. DIY - DI. Agenda:. Dependency Injection - trochę podstaw Kontenery DI - masz problem, weź framework Service Locator - anti-pattern Do-It-Yourself Dependency Injection. Dependency Injection - trochę podstaw. Definicje z WIKI. - PowerPoint PPT Presentation

Transcript of DIY - DI

DIY - DIAdam Suwała

2

Agenda:

Dependency Injection - trochę podstaw Kontenery DI - masz problem, weź framework Service Locator - anti-pattern Do-It-Yourself Dependency Injection

DEPENDENCY INJECTION - TROCHĘ PODSTAW

4

Definicje z WIKI

• Wstrzykiwanie zależności (Dependency Injection, DI) – wzorzec projektowy i wzorzec architektury oprogramowania polegający na usuwaniu bezpośrednich zależności pomiędzy komponentami na rzecz architektury typu plug-in.

• Odwrócenie sterowania (Inversion of Control, IoC) – paradygmat polegający na przeniesieniu na zewnątrz komponentu (np. obiektu) odpowiedzialności za kontrolę wybranych czynności.

5

Po co DI?

sprawić żeby kod miał miej powiązań lub powiązania były „luźniejsze” (loose coupling).

ułatwić testy jednostkowe kodu (automatyczne testy pojedynczych klas w izolacji od innych)

6

Rodzaje wstrzykiwania zależności: Constructor Injection Setter Injection Interface Injection

7

Jak to wygląda w praktyce?

Tradycyjne podejście:

class Mail{//… public bool Wyslij() { var klientSmtp = new KlientSmtp(); return klientSmtp.Wyslij(adres, tytul,

tresc); }//…}

8

Podejście DI:

class Mail{ private readonly IKlientSmtp klientSmtp;

public Mail(IKlientSmtp klientSmtp) { this.klientSmtp = klientSmtp; }//… public bool Wyslij() { var klientSmtp = new KlientSmtp(); return klientSmtp.Wyslij(adres, tytul, tresc); }//…}

9

Skąd w DI biorą się zależności?

class Mail{ private readonly IKlientSmtp klientSmtp;

public Mail(IKlientSmtp klientSmtp) { this.klientSmtp = klientSmtp; }//…}

KONTENERY - MASZ PROBLEM, WEŹ FRAMEWORK

11

Frameworki DI dla .net, przykłady:

• Autofac

• Castle.Windsor

• Ninject

• Sprint.net

• StructureMap

• Unity

12

Świadomy wybór kontenera DI

Różnice:

• Składnia

• Wsparcie dla różnych rozwiązań

• Szybkość działania

13

Co fajnego potrafią robić kontenery DI?

14

Kaskada zależności – powiązania klasclass KlientTcp : IKlientTcp { /*…*/ }class KlientSmtp : IKlientSmtp { private readonly IKlientTcp klientTcp;

public KlientSmtp(IKlientTcp klientTcp) { this.klientTcp = klientTcp; } /*…*/ }class Mail { private readonly IKlientSmtp klientSmtp;

public Mail(IKlientSmtp klientSmtp) { this.klientSmtp = klientSmtp; } /*…*/}

15

Kaskada zależności – jak złożyć?

class UzycieKontenera { void Przyklad() {

Kontener.Register<IKlientTcp, KlientTcp>(); Kontener.Register<IKlientSmtp, KlientSmtp>(); //…

var mail = Kontener.Resolve<Mail>(); //… }}

16

Metody inicjowania kontenera

Bezpośrednie rejestrowanie w kodzie Pliki konfiguracyjne Z użyciem refleksji

17

Czas życia obiektów

Transient Singleton Thread Singleton Request Singleton

18

Dlaczego DI bez kontenera?

Nie jest konieczny do robienia DI Może zaciemniać kod Rzeczywiste zależności mogą nie być

tak proste jak w przykładach Może prowokować do robienie DI źle

SERVICE LOCATOR - ANTI-PATTERN

20

Jak wygląda wzorzec Service Locator:class Mail{//… public bool Wyslij() { var klientSmtp = Kontener.Resolve<IKlientSmtp>(); return klientSmtp.Wyslij(adres, tytul, tresc); }//…}

DO-IT-YOURSELF DEPENDENCY INJECTION

22

„Chad Parry DIY-DI”

– jak zrobić dobre DI bez kontenera.

23

DI powinniśmy używać konsekwentnie w całej aplikacji.

DI to sposób myślenia

24

Scope – techniczna klasa, która ma przechowywać parametry konfiguracyjne i uchwyty do obiektówclass ApplicationScope{ private readonly string[] args;

public ApplicationScope(string[] args) { this.args = args; MaxX = 100; MaxY = 100; }

public string[] Args { get { return args; } }

public int MaxX { get; private set; } public int MaxY { get; private set; }}

25

MainHelper – „biznesowo-pomocnicza” klasa, która umożliwia wstrzykiwanie zależności od samego startu programu

class MainHelper{ private readonly string[] args; private readonly IRobot robot;

public MainHelper(string[] args, IRobot robot) { this.args = args; this.robot = robot; }

public void Run() { // to co normalnie jest w main }}

26

Injector – techniczna klasa, której zadaniem jest składanie aplikacji i właściwe wstrzykiwanie zależności

class ApplicationInjector{ public MainHelper InjectMainHelper(ApplicationScope scope) { return new MainHelper(scope.Args, InjectRobot(scope)); }

private IRobot InjectRobot(ApplicationScope scope) { return new Robot(scope.MaxX, scope.MaxY); }}

27

Start programu

class Program{ static void Main(string[] args) { var scope = new ApplicationScope(args);

new ApplicationInjector() .InjectMainHelper(scope) .Run(); }}

28

Co zrobić ze skomplikowanymi przypadkami?

29

Fabryka – ale nie taka zwyczajna

public class FabrykaRobotow{ private readonly Func<IRobot> robotBuild; private readonly Func<IRobot> robotLatajacyBuild;

internal FabrykaRobotow(Func<IRobot> robotBuild, Func<IRobot> robotLatajacyBuild) { this.robotBuild = robotBuild; this.robotLatajacyBuild = robotLatajacyBuild; }

public IRobot ZbudujRobota() { bool decyzja = new Random().Next(1) == 0; if (decyzja) return robotBuild(); else return robotLatajacyBuild(); }}

30

Użycie fabrykiclass ApplicationInjector{ public MainHelper InjectMainHelper(ApplicationScope scope) { return new MainHelper( scope.Args, InjectFabrykaRobotow(scope).ZbudujRobota()); } private FabrykaRobotow InjectFabrykaRobotow(ApplicationScope

scope) { return new FabrykaRobotow( () => InjectRobot(scope), () => InjectRobotLatajacy(scope)); } private IRobot InjectRobot(ApplicationScope scope) { /*…*/ } private IRobot InjectRobotLatajacy(ApplicationScope scope)

{ /*…*/ }}

31

Uchwyty do obiektów w Scope

32

Uchwyt do obiektu w Scopeclass ScopeCache<T>{ private readonly object @lock = new object(); private volatile bool full; private T cache;

public T Get(Func<T> initiator) { if (!full) { lock (@lock) { if (!full) { cache = initiator(); full = true; } } } return cache; }}

33

Użycie ScopeCache w Scope

class ApplicationScope{//… public readonly ScopeCache<FabrykaRobotow> FabrykaRobotowCache

= new ScopeCache<FabrykaRobotow>(); //…}

34

Inicjacja obiektu w Injector

class ApplicationInjector{//… private FabrykaRobotow InjectFabrykaRobotow(ApplicationScope

scope) { return scope.FabrykaRobotowCache.Get(() => new FabrykaRobotow( () => InjectRobot(scope), () => InjectRobotLatajacy(scope) ) ); }//…}

35

Uchwyt „per-wątek”

public class ScopeThreadCache<T>{ private readonly ThreadLocal<T> cache = new

ThreadLocal<T>();

public T Get(Func<T> initiator) { if (!cache.IsValueCreated) { cache.Value = initiator(); } return cache.Value; }}

36

Jeszcze raz: konsekwencja w stosowaniu DI.

37

Mniej lub prostsze Mock’i w testach jednostkowych.

38

Problem z kontekstami.

39

Robot - wywołanie

class ApplicationInjector{//… private IRobot InjectRobot(ApplicationScope

scope) { return new Robot(scope.MaxX, scope.MaxY); }//…}

40

Robot – definicja klasy

public class Robot : IRobot{

private readonly int maxX; private readonly int maxY;

internal Robot(int maxX, int maxY) { this.maxX = maxX; this.maxY = maxY; }

public bool IdzDo(int x, int y) { // zrób co trzeba return true; }}

41

Jeżeli parametrów było by zbyt wielepublic class Robot : IRobot{ private readonly RobotLimits limits;

internal Robot(RobotLimits limits) { this.limits = limits; }//…}public class RobotLimits{ public int MaxX { get; set; } public int MaxY { get; set; }//…}

42

Komponenty aplikacji mogą mieć własną parę klas Scope-Injector.

43

Para Scope – Injector dla Komponentuclass ComponentScope{ internal readonly ScopeCache<KlientSmtp> KlientSmtpCache = new ScopeCache<KlientSmtp>(); internal readonly ScopeCache<KlientTcp> KlientTcpCache = new ScopeCache<KlientTcp>();}

class ComponentInjector{ public IKlientSmtp InjectKlientSmtp(ComponentScope scope) { return scope.KlientSmtpCache.Get(() => new KlientSmtp(InjectKlientTcp(scope))); }

private IKlientTcp InjectKlientTcp(ComponentScope scope) { return scope.KlientTcpCache.Get(() => new KlientTcp()); }}

44

Podpięcie komponentu w ApplicationScope

class ApplicationScope{//… public readonly ScopeCache<ComponentInjector>

ComponentInjectorCache = new ScopeCache<ComponentInjector>();

public readonly ScopeCache<ComponentScope> ComponentScopeCache = new ScopeCache<ComponentScope>(); }

45

Użycie komponentu w ApplicationInjectorclass ApplicationInjector{ //… private Mail InjectMail(ApplicationScope scope) { return new Mail(InjectKlientSmtp(scope)); } private IKlientSmtp InjectKlientSmtp(ApplicationScope scope) { return InjectComponentInjector(scope) .InjectKlientSmtp(InjectComponentScope(scope)); } private ComponentInjector InjectComponentInjector(ApplicationScope

scope) { return scope.ComponentInjectorCache.Get(() => new ComponentInjector()); } private ComponentScope InjectComponentScope(ApplicationScope scope)

{ return scope.ComponentScopeCache.Get(() => new

ComponentScope()); } }

46

Pytania?

47

Źródła

http://pl.wikipedia.org/wiki/Wstrzykiwanie_zale%C5%BCno%C5%9Bci

http://www.martinfowler.com/articles/injection.html

http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx

http://philipm.at/2011/0808/ --- testy wydajnościowe kontenerów

http://misko.hevery.com/2010/05/26/do-it-yourself-dependency-injection/

http://blacksheep.parry.org/wp-content/uploads/2010/03/DIY-DI.pdf