Post on 06-Feb-2016
description
2
Agenda:
Dependency Injection - trochę podstaw Kontenery DI - masz problem, weź framework Service Locator - anti-pattern Do-It-Yourself Dependency Injection
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)
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; }//…}
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
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
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
20
Jak wygląda wzorzec Service Locator:class Mail{//… public bool Wyslij() { var klientSmtp = Kontener.Resolve<IKlientSmtp>(); return klientSmtp.Wyslij(adres, tytul, tresc); }//…}
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(); }}
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)
{ /*…*/ }}
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; }}
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; }//…}
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()); } }
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