Pár postřehů k Dependency Injection

Jak známo, existuje více způsobu, jak zajistit nainjectování závislostí do konkrétní třídy. Nejčastější jsou IMHO constructor injection (injectované typy jsou typy parametrů konstruktoru) a property setter injection (injectované typy jsou typy properties). Problém, který mám s druhým typem, je ten, že kontejner musí při konstrukci objektu vědět, které properties má zpracovat. To dává klasicky třída najevo tím, že se daná property označí atributem, specifickým pro jeden konkrétní kontejner. A právě takové zabordelení je to, co se mi na property setter injection nelíbí.
Nabízí se ale několik řešení, jak se vyhnout použití specifických atributů a tím snížit zabordelení kódu.

  • Můžeme si vymyslet vlastní univerzální atribut, kterým budeme označovat properties, do kterých se má injectovat. Pak ale musíme zajistit, aby tomuto atributu aktuálně použitý kontejner rozuměl.
  • Zavést si konvenci, že všechny write-only properties jsou určené pro injectování. Mám na mysli write-only properties z public pohledu, tedy properties, kde getter buď úplně chybí nebo má menší viditelnost než setter. Vzhledem k tomu, že jsem nikdy v běžném kódu takovou write-only property nepoužil, neměl bych s tímto přístupem velký problém. I v tomto případě musíme rozšířit kontejner tak, aby této konvenci rozuměl.
  • Někoho by mohlo napadnout nakonfigurovat kontejner tak, aby pro konkrétní třídu injectoval vyjmenované properties (což by se dalo v kódu udělat i pěkně silně typově :)). Toto řešení ale nepovažuji za šťastné, neboť si myslím, že o tom, jaké properties jsou určeny pro injectování, by měla rozhodovat třída sama, nikoliv její uživatel (skrzevá konfiguraci).

S constructor injection žádný takový bordelizační problém nemám. Ale můžeme se dostat do situace, kdy jedna třída potřebuje ke své funkčnosti druhou třídu et vice versa (že to může být důsledek špatného designu aplikace nyní nechme stranou).

public class Service1 : IService1
{
  public Service1(IService2 service2)
  {
     // ...
  }
}

public class Service2 : IService2
{
  public Service2(IService1 service1)
  {
     // ...
  }
}

Když budeme po kontejneru chtít získat IService1 nebo IService2, tak dostaneme StackoverflowException, v lepším případě nám chytrý kontejner hned zahlásí, že detekoval cyklus.
Jak z toho ven? Jednoduše musíme vyhodnocení závislostí, které způsobují zacyklení, nějakým způsobem odložit. Pokud používáme .NET Framework 4.0+, máme k dispozici třídu Lazy<T>. Pak můžeme změnit typ parametru z IService1 na Lazy<IService1>. Když pak budeme chtít implementaci IService1 opravdu potřebovat, použijeme property Value. Toto si můžeme elegantně schovat za privátní property.
Jak vidno, kód jsme si trošku zabordelili. Ale cyklické závislosti by opravdu neměly být na denním pořádku, takže i tento způsob zabordelení by se měl vyskytovat zřídka.

Síla odloženého vyhodnocení závislostí ale dle mého skromného názoru tkví v něčem jiném… Každá třída deklaruje nějakým způsobem (např. parametry konstruktoru), jaká rozhraní potřebuje ke své funkčnosti. Když pak někde takovou třídu potřebujeme, tak abychom ji mohli zkonstruovat, potřebujeme vyhodnotit také všechny parametry konstruktoru. Ale co když některou ze závislých služeb používáme jen někdy? Není pak zbytečné vytvářet onu třídu, když ji nakonec třeba ani nepoužijeme?

public class Service1 : IService1
{
  protected readonly IService2 _Service2;
  public Service1(IService2 service2)
  {
     _Service2 = service2;
  }

  public void DoSomething()
  {
    if (SomeState)
    {
       _Service2.DoSomethingElse();
    }
  }
}

}

A právě zde bych použil raději typ Lazy<IService2>.

Pokud používáte starší verzi .NET Frameworku, tak nemusíte zoufat – implementace typu Lazy<T> je jednoduchá. Osobně používám tuto svou implementaci, která se svým chováním trošku liší od implementace v .NET FW 4.0 (nekešuje si vrácenou hodnotu).

public interface ILazy<T>
{
	T Resolve();
	T Resolve(string key);
}

public class Lazy<T> : ILazy<T>
{
	readonly IServiceLocator _Container;

	public Lazy(IServiceLocator container)
	{
		this._Container = container;
	}

	public Lazy(IServiceLocator container, string key)
	{
		this._Container = container;
		this.Key = key;
	}

	public string Key { get; private set; }

	public T Resolve()
	{
		return string.IsNullOrEmpty(Key) ? _Container.GetInstance<T>() : _Container.GetInstance<T>(Key);
	}

	public T Resolve(string key)
	{
		return _Container.GetInstance<T>(key);
	}
}

Dále je třeba toto rozhraní zaregistrovat v kontejneru, v případě Unity takto:

container.RegisterType(typeof(ILazy<>), typeof(Lazy<>), new InjectionConstructor(new ResolvedParameter<IServiceLocator>()));

Jak vidno, nepoužívám v mé třídě k resolvování Unity napřímo, ale mám ho abstrahované pomocí rozhraní IServiceLocator z projektu CommonServiceLocator, které vypadá následovně (abstrahuje pouze vyhodnocování, nikoliv registraci):

public interface IServiceLocator : IServiceProvider
{
	object GetInstance(Type serviceType);
	object GetInstance(Type serviceType, string key);
	
	TService GetInstance<TService>();
	TService GetInstance<TService>(string key);

	IEnumerable<object> GetAllInstances(Type serviceType);
	IEnumerable<TService> GetAllInstances<TService>();
}

Varuji ale před použitím tohoto rozhraní někde jinde než v takovýchto infrastrukturních záležitostech. Rozhodně by se nemělo vyskytnout v běžném kódu. Mohlo by totiž svádět k tomu, abychom ho použili jako univerzální ILazy<T> – to opravdu nedoporučuji. Raději do konstruktoru vyjmenujte těch X typů, které třída používá – na první pohled pak bude vidět, jaké má třída závislosti.

http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js
http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js

if (SyntaxHighlighter) {
SyntaxHighlighter.all();
}

3 thoughts on “Pár postřehů k Dependency Injection

  1. Windsor injektuje properties se setterem automaticky, pokud dokáže vyhodnotit typ té vlastnosti. To se dá využít injektáži nepovinných závislostí. Kounstruktorová injekce je dobrá na povinné závislosti.

    Místo harakiri s Lazy of T by spíš zvolil injekci Factory of T. 🙂

    To se mi líbí

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.