dependency injection v .net frameworku
DESCRIPTION
Dependency injection v .Net bez pověr, iluzí a frikulínského nadšení.TRANSCRIPT
DI v .NET bez pověr, iluzí a frikulínského nadšení
René Steinhttp://renestein.net
http://twitter.com/renestein
MS FEST 2012
Na této přednášce nebude pomlouváno Metro (ani kdyby se jmenovalo Modern UI)!
Pracujeme se závislostmi - v aplikacích
• A rovnou se na takovou aplikaci podíváme (bez pomoci Karla Nešpora)
Každý problém se závislostmi vyřeší on!
Náš chrabrý Singleton!
Bůh je stejně jako singleton vždy jen jeden. Skeptická poznámka: dokud nejsou
oba minimálně trojjediní.
Singleton v aplikaci
• Ukázka singletonu• Ukázka komplikovanějšího singletonu
složeného z dalších singletonů – SimpleServiceHolder.
Singleton(y)Poskytuje globální přístupový bod k
jedné instanci. Ale:• Skryté závislosti v kódu aplikace.• Malá kontrola nad životním cyklem
singletonu (jen ta „lazy“ inicializace).• Problémy se singletony ve
vícevláknovém prostředí.• Problémy se singletony v unit
testech.
Repozitář služeb• Vulgo „service locator“• Nabízí „služby všeho druhu“ • Redukuje větší množství singletonů
v aplikaci na jeden singleton
Repozitář služeb - nevýhody
Objekt, který ve svém rozhraní slibuje víc, než může splnit.
Ve svém rozhraní nabízí všechny služby na světě (a možná i mimo něj).
Závislosti třídy nepoznáme z jejího rozhraní, protože komunikace s repozitářem služeb je utopena v privátní implementaci třídy.
Repozitář služeb – nevýhody II
Nepřehledný kód SimpleServiceRepository.Instance.GetService<X> - to je všudypřítomná náhrada za volání konstruktoru - new X().
SimpleServiceRepository.Instance.GetService< ….>
• Všechny knihovny většinouzávisí na knihovně s SimpleServiceRepository.
Repozitář služeb – nevýhody III
• Nikdy se nemůžete spolehnout na to, že služba je repozitáři registrována.
• Víte, co je „Ambient context“? var errService =
SimpleServiceRepository.Instance.GetService<IWorkflowErrorInfoProviderEx>(); - na jiném místě
if(errService != null) { errService.PublishError(….); }
Dependency injection
Odstranění skrytých závislostí
Odstranění těsných vazeb mezi objekty
(Snadné) mapování abstrakcí na
implementaci
Spolupráce objektů s přesně vymezenou odpovědností (SRP)
Injektování přes konstruktor
var myOrderService = new OrderService (myRepository);class OrderService{ public OrderService(IRepository<Order> repository) { if(repository == null) { throw ….; } m_repository = repository; }….}
Injektujeme přes konstruktor
Předávání závislostí, bez kterých instance nemůže existovat
Preferujte předávání závislostí přes konstruktor
Injektujeme přes vlastnostpublic class ExportService{ public ExportService() { ConfigService = new DefaultConfigService(); }
public IConfigService ConfigService {
get; set;
} public void ExportData { …. ConfigService.GetValue(…);}
}
Kdy injektovat přes vlastnost?
Když je závislost nepovinná. Ale pozor na NullReferenceException při používání závislosti!
Když máte smysluplnou výchozí implementaci (i Null object) => ve třídě se ale svážeme s další konkrétní třídou (new (konkrétní) ZavislostX())
Pozor na problémy s automatickým injektováním
závislostí přes vlastnosti
interface IViewModel { IView CurrentView { get; } }
//ViewModelBase [DoNotWire]public IView CurrentView { get; set; }
Injektování přes argument metody
public void RunTransition (IWorkflowContext context)
{ if(context == null) { throw ….; } …context.ApplyTransitionRules(currentState,
this);}
Kdy injektovat přes argument metody
Při každém volání metody potřebujeme odlišný objekt
Předávaný objekt většinou představuje unikátní kontext jednoho volání metody
S tímto druhem závislostí nám DI kontajner nepomůže
DI kontajner
DI kontajner si představme jako nástroj, který nám zjednodušuje vytváření objektových grafů.
Jako nástroj, pomocí kterého snadno propojíme nezávislé části naší aplikace a splníme jim jejich přání = dodáme jim všechny jejich závislosti.
Co očekávat od DI kontajneru?
SOLIDní kompozice
objektů
Správa životního cyklu služeb, resp. komponent
(Singleton, PerThread, PerWebRequest,
Transient)
Intercepce metod (skvělá podpora návrhového vzoru
Decorator)
Použití DI kontajneru – 3 RRegister
R =Registrace
Jednorázová registrace abstrakcí na implementace (registrace komponent a služeb) v takzvaném „DI kompozičním centru “ (composition root) po startu aplikace.
R = registrace• Preferujte konfiguraci v kódu.
m_container.Register(Component.For(typeof(IRepository<>)) .ImplementedBy(typeof(DefaultEFRepository<>)));
• Registrace v XML konfiguračním souboru jen tehdy, když chcete přidávat „pluginy“.
Registrace v XML – Castle Windsor („pluginy – pozdní vazba “)<components> <component id= "MyLogService" service= "CommonServices.ILogger,
CommonServices" type="DefaultServices.DbLogger,
DefaultServices" </component></components>
R = Registrace - další doporučení
Preferujte hromadné registrace s využitím konvencí místo explicitní registrace každé služby
Registrace na základě jmenných konvencí
Predicate<Type> servicesCondition = type => type.Name.Contains(PROVIDER_SUFFIX) || type.Name.Contains(SERVICE_SUFFIX);
m_container.Register(Classes.FromAssembly. Containing(typeof(INewObjectIdProvider<>))
.Where(servicesCondition) .WithServiceDefaultInterfaces() .WithServiceSelf()
.LifestyleSingleton());
Použití DI kontajneru – 3 RResolve
R = Resolve
Získání služby z DI kontajneru. Službu mohu
ihned používat, její závislosti splnil DI
kontajner
ALE:
Princip neviditelnosti DI kontajneru
Nikdy nepodlehněte pokušení používat DI kontajner jako service locator (repozitář služeb).
Nikdy se ve svém aplikačním kódu nereferencujte DI kontajner. K tomu vám dopomáhej zdravý vývojářský úsudek a vzor Abstract Factory.
Metodu „Resolve“ na DI kontajneru volá jen „composition root“ a infrastrukturní objekty (ControllerFactory v ukázce).
DI kontajner není service locator!
public MainViewModel(IWindsorContainer container){ ….}var childView =
container.Resolve<IChildView>;
public MainViewModel(IViewFactory viewfactory){…. }var childView =viewfactory.Create<IChildView>();
Abstraktní továrna ve Windsor Castlu
• Castle dokáže typické abstraktní továrny generovat sám s využitím dynamické proxy.
m_container.AddFacility<TypedFactoryFacility>();
m_container.Register(Component.For<IViewFactory>().AsFactory<IViewFactory>());
Použití DI kontajneru – 3 RRelease
• Cokoli vyzvednu „manuálně“ z DI kontajneru (Resolve) uvolním metodou Release.
public override void ReleaseController(IController controller)
{ m_container.Release(controller); base.ReleaseController(controller); }• Životní cyklus služeb řídí DI kontajner.
Mýty o dependency injection (a IoC)DI je komplikovaná záležitost .(A já jsem duše prostá
)
Nepíšu unit testy (!) => nepotřebuju DI
Kdo nepoužívá DI (nebo o ní alespoň nemluví) není pravý vývojář
DI == DI kontajner
Říká se tomu DI kontajner, ale já vím, že je to jen vylepšená abstraktní továrna
Mýty o dependency injection
• V konstruktoru se mi budou množit argumenty => jednoduchý detektor porušení SRP.
public GodService(IWorldBuilder worldbuilder, IHelloWorld initWorldService,IEdenService edenService,IJesusChristSupport jesusSupportServiceIApocalypseRunner apocalypseRunner)
• Víte, co jsou kompozitní služby?
DI kontajnery v .Net světě
StructureMap
Castle Windsor Hiro
Spring.NET
Autofac Unity
Ninject
?MEF?
DI kontajnery – podobnosti a rozdíly
Svatou DI trojici Register/Resolve/Release zvládne každý DI kontajner.
Pak má ale každý kontajner své unikátní vlastnosti –Windsor Castle vyniká při intercepci pomocí dynamické proxy nebo se s jeho pomocí skvěle diagnostikují chyby při registraci služeb…
René Stein
• Vývoj aplikací, veřejné a inhouse kurzy• http://www.renestein.net/nabidka.aspxhttp://blog.renestein.nethttp://twitter.com/renestein