1 Adam Suwała DIY - DI
2 Agenda: Dependency Injection - trochę podstawKontenery DI - masz problem, weź framework Service Locator - anti-pattern Do-It-Yourself Dependency Injection
3 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; } //…
10 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 DIRóż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
16 Metody inicjowania konteneraBezpośrednie rejestrowanie w kodzie Pliki konfiguracyjne Z użyciem refleksji
17 Czas życia obiektów Transient Singleton Thread SingletonRequest 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
19 Service Locator - anti-pattern
20 Jak wygląda wzorzec Service Locator:class Mail { //… public bool Wyslij() var klientSmtp = Kontener.Resolve
21 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 zwyczajnapublic class FabrykaRobotow { private readonly Func
30 Użycie fabryki class 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
33 Użycie ScopeCache w Scopeclass ApplicationScope { //… public readonly ScopeCache
34 Inicjacja obiektu w Injectorclass ApplicationInjector { //… private FabrykaRobotow InjectFabrykaRobotow(ApplicationScope scope) return scope.FabrykaRobotowCache.Get(() => new FabrykaRobotow( () => InjectRobot(scope), () => InjectRobotLatajacy(scope) ) ); }
35 Uchwyt „per-wątek” public class ScopeThreadCache
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 klasypublic 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
44 Podpięcie komponentu w ApplicationScopeclass ApplicationScope { //… public readonly ScopeCache
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 testy wydajnościowe kontenerów