470 likes | 687 Views
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.
E N D
Adam Suwała DIY - DI
Agenda: • DependencyInjection - trochę podstaw • Kontenery DI - masz problem, weź framework • Service Locator - anti-pattern • Do-It-YourselfDependencyInjection
Definicje z WIKI •Wstrzykiwanie zależności (DependencyInjection, 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.
Po co DI? • sprawić żeby kod miał miej powiązań lub powiązania były „luźniejsze” (loosecoupling). • ułatwić testy jednostkowe kodu (automatyczne testy pojedynczych klas w izolacji od innych)
Rodzaje wstrzykiwania zależności: • ConstructorInjection • SetterInjection • InterfaceInjection
Jak to wygląda w praktyce? Tradycyjne podejście: class Mail { //… public boolWyslij() { varklientSmtp = newKlientSmtp(); return klientSmtp.Wyslij(adres, tytul, tresc); } //… }
Podejście DI: class Mail { privatereadonlyIKlientSmtpklientSmtp; public Mail(IKlientSmtpklientSmtp) { this.klientSmtp = klientSmtp; } //… public boolWyslij() { varklientSmtp = newKlientSmtp(); return klientSmtp.Wyslij(adres, tytul, tresc); } //… }
Skąd w DI biorą się zależności? class Mail { privatereadonlyIKlientSmtpklientSmtp; public Mail(IKlientSmtpklientSmtp) { this.klientSmtp = klientSmtp; } //… }
Frameworki DI dla .net, przykłady: • Autofac • Castle.Windsor • Ninject • Sprint.net • StructureMap • Unity
Świadomy wybór kontenera DI Różnice: • Składnia • Wsparcie dla różnych rozwiązań • Szybkość działania
Kaskada zależności – powiązania klas classKlientTcp : IKlientTcp { /*…*/ } classKlientSmtp : IKlientSmtp { privatereadonlyIKlientTcpklientTcp; public KlientSmtp(IKlientTcpklientTcp) { this.klientTcp = klientTcp; } /*…*/ } class Mail { privatereadonlyIKlientSmtpklientSmtp; public Mail(IKlientSmtpklientSmtp) { this.klientSmtp = klientSmtp; } /*…*/ }
Kaskada zależności – jak złożyć? classUzycieKontenera { voidPrzyklad() { Kontener.Register<IKlientTcp,KlientTcp>(); Kontener.Register<IKlientSmtp, KlientSmtp>(); //… var mail = Kontener.Resolve<Mail>(); //… } }
Metody inicjowania kontenera • Bezpośrednie rejestrowanie w kodzie • Pliki konfiguracyjne • Z użyciem refleksji
Czas życia obiektów • Transient • Singleton • Thread Singleton • Request Singleton
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
Jak wygląda wzorzec Service Locator: class Mail { //… public boolWyslij() { varklientSmtp = Kontener.Resolve<IKlientSmtp>(); return klientSmtp.Wyslij(adres, tytul, tresc); } //… }
„Chad Parry DIY-DI” – jak zrobić dobre DI bez kontenera.
DI powinniśmy używać konsekwentnie w całej aplikacji. • DI to sposób myślenia
Scope – techniczna klasa, która ma przechowywać parametry konfiguracyjne i uchwyty do obiektów classApplicationScope { privatereadonlystring[] args; public ApplicationScope(string[] args) { this.args = args; MaxX = 100; MaxY = 100; } public string[] Args { get { return args; } } public intMaxX { get; private set; } public intMaxY { get; private set; } }
MainHelper – „biznesowo-pomocnicza” klasa, która umożliwia wstrzykiwanie zależności od samego startu programu classMainHelper { privatereadonlystring[] args; privatereadonlyIRobot robot; public MainHelper(string[] args, IRobot robot) { this.args = args; this.robot = robot; } public void Run() { // to co normalnie jest w main } }
Injector – techniczna klasa, której zadaniem jest składanie aplikacji i właściwe wstrzykiwanie zależności classApplicationInjector { public MainHelperInjectMainHelper(ApplicationScopescope) { return newMainHelper(scope.Args, InjectRobot(scope)); } privateIRobotInjectRobot(ApplicationScopescope) { return new Robot(scope.MaxX, scope.MaxY); } }
Start programu class Program { staticvoidMain(string[] args) { varscope = newApplicationScope(args); newApplicationInjector() .InjectMainHelper(scope) .Run(); } }
Fabryka – ale nie taka zwyczajna public classFabrykaRobotow { privatereadonlyFunc<IRobot> robotBuild; privatereadonlyFunc<IRobot> robotLatajacyBuild; internalFabrykaRobotow(Func<IRobot>robotBuild, Func<IRobot> robotLatajacyBuild) { this.robotBuild = robotBuild; this.robotLatajacyBuild = robotLatajacyBuild; } public IRobotZbudujRobota() { bool decyzja = new Random().Next(1) == 0; if (decyzja) returnrobotBuild(); else return robotLatajacyBuild(); } }
Użycie fabryki classApplicationInjector { public MainHelperInjectMainHelper(ApplicationScopescope) { return newMainHelper( scope.Args, InjectFabrykaRobotow(scope).ZbudujRobota()); } privateFabrykaRobotowInjectFabrykaRobotow(ApplicationScopescope) { return newFabrykaRobotow( () => InjectRobot(scope), () => InjectRobotLatajacy(scope)); } privateIRobotInjectRobot(ApplicationScopescope) { /*…*/ } privateIRobotInjectRobotLatajacy(ApplicationScopescope) { /*…*/ } }
Uchwyt do obiektu w Scope classScopeCache<T> { privatereadonlyobject @lock = newobject(); privatevolatileboolfull; private T cache; public T Get(Func<T> initiator) { if (!full) { lock (@lock) { if (!full) { cache = initiator(); full = true; } } } return cache; } }
Użycie ScopeCache w Scope classApplicationScope { //… public readonlyScopeCache<FabrykaRobotow>FabrykaRobotowCache = newScopeCache<FabrykaRobotow>(); //… }
Inicjacja obiektu w Injector classApplicationInjector { //… privateFabrykaRobotowInjectFabrykaRobotow(ApplicationScopescope) { return scope.FabrykaRobotowCache.Get(() => newFabrykaRobotow( () => InjectRobot(scope), () => InjectRobotLatajacy(scope) ) ); } //… }
Uchwyt „per-wątek” public classScopeThreadCache<T> { privatereadonlyThreadLocal<T> cache = newThreadLocal<T>(); public T Get(Func<T> initiator) { if (!cache.IsValueCreated) { cache.Value = initiator(); } return cache.Value; } }
Robot - wywołanie classApplicationInjector { //… privateIRobotInjectRobot(ApplicationScopescope) { return new Robot(scope.MaxX,scope.MaxY); } //… }
Robot – definicja klasy public class Robot : IRobot { privatereadonlyintmaxX; privatereadonlyintmaxY; internal Robot(intmaxX, intmaxY) { this.maxX = maxX; this.maxY = maxY; } public boolIdzDo(int x, int y) { // zrób co trzeba return true; } }
Jeżeli parametrów było by zbyt wiele public class Robot : IRobot { privatereadonlyRobotLimitslimits; internal Robot(RobotLimitslimits) { this.limits = limits; } //… } public classRobotLimits { public intMaxX { get; set; } public intMaxY { get; set; } //… }
Komponenty aplikacji mogą mieć własną parę klas Scope-Injector.
Para Scope – Injector dla Komponentu classComponentScope { internalreadonlyScopeCache<KlientSmtp> KlientSmtpCache = newScopeCache<KlientSmtp>(); internalreadonlyScopeCache<KlientTcp> KlientTcpCache = newScopeCache<KlientTcp>(); } classComponentInjector { public IKlientSmtpInjectKlientSmtp(ComponentScopescope) { return scope.KlientSmtpCache.Get(() => newKlientSmtp(InjectKlientTcp(scope))); } privateIKlientTcpInjectKlientTcp(ComponentScopescope) { return scope.KlientTcpCache.Get(() => newKlientTcp()); } }
Podpięcie komponentu w ApplicationScope classApplicationScope { //… public readonlyScopeCache<ComponentInjector> ComponentInjectorCache = newScopeCache<ComponentInjector>(); public readonlyScopeCache<ComponentScope> ComponentScopeCache = newScopeCache<ComponentScope>(); }
Użycie komponentu w ApplicationInjector classApplicationInjector { //… private Mail InjectMail(ApplicationScopescope) { return new Mail(InjectKlientSmtp(scope)); } privateIKlientSmtpInjectKlientSmtp(ApplicationScopescope) { return InjectComponentInjector(scope) .InjectKlientSmtp(InjectComponentScope(scope)); } privateComponentInjectorInjectComponentInjector(ApplicationScopescope) { return scope.ComponentInjectorCache.Get(() => newComponentInjector()); } privateComponentScopeInjectComponentScope(ApplicationScopescope) { return scope.ComponentScopeCache.Get(() => newComponentScope()); } }
Ź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