Kamil Kliczbor @ asptip.net

3Nov/130

Castle Windsor – wprowadzenie

Castle Windsor

Wstęp

WindsorCastle jest kontenerem wstrzykiwania zależności. Jest to jeden z kilku projektów http://www.castleproject.org/. W tym wpisie krótko opiszę w jaki sposób ten temat ugryźć (delikatnie).

Prosta aplikacja dla fanów motoryzacji

Poniższy przykład trochę naiwnie pokazuje użycie kontenera w aplikacji. W tym konkretnym przypadku chcemy stworzyć moduł bazowy dla producenta samochodów, który dostarcza standardowo wykonany samochód wyposażony w standardowy silnik. W aplikacji chcemy mieć możliwość modyfikacji zarówno pojazdu jak i silnika w zależności od zapotrzebowania lokalnego sprzedawcy. Pomiędzy pojazdem i silnikiem zachodzą jednak pewne zależności, a ściślej rzecz ujmując samochód umie współpracować z silnikiem poprzez znany interfejs, a silnik jest kluczowy podzespołem samochodu . To co będzie się działo w samym silniku zależy od realizacje jego wykonania. Dla przykładu samochód może posiadać silnik spalinowy, elektryczny, hybrydowy etc. Takie podejście w aplikacjach biznesowych jest często stosowane i nie powinno być zaskoczeniem.
W naszej aplikacji zdefiniowaliśmy interfejs ICar, a w zależności od konfiguracji kontenera chcemy podać do jego konstruktora jako zależność jakąś konkretną implementację interfejsu IEngine. Zwróćmy uwagę, że w definicji konstruktora typem przekazywanego parametru jest interfejs, a nie jego implementacja. W najprostszym przypadku mamy do czynienia tylko z jedną implementacją interfejsu ICar i IEngine. Ale może się zdarzyć, że dla różnych implementacji ICar chcemy wstrzyknąć różne implementacje interfejsu IEngine.

public interface IEngine
{
	int HorsePower { get; }
}

public class Engine : IEngine
{
	public int HorsePower
	{
		get
		{
			return 100;
		} 
	}
}

public interface ICar
{
	int GetPower();
}

public class Car : ICar
{
	private readonly IEngine _engine;

	public Car(IEngine engine)
	{
		_engine = engine;
	}

	public int GetPower()
	{
		return _engine.HorsePower;
	}
}

Logika klasy Car jest dosyć prosta w tym przypadku, ale równie dobrze można by zastosować bardziej skomplikowany algorytm. Co więcej, interfejs ICar zapewnia jedynie kontrakt, a zwracaną implementację interfejsu ICar można skonfigurować w kontenerze.

Byty w Castle Windsor

Castle w odniesieniu do powyższych interfejsów i klas wprowadza trzy pojęcia:

  • serwis - jest to abstrakcyjny kontrakt opisujący spójną jednostkę funkcjonalności; innymi słowy w naszej aplikacji interfejsy ICar oraz IEngine są serwisami, gdyż definiują tylko kontrakt, jaki implementujące je klasy muszą spełnić. W przypadku interfejsu IEngine implementujące klasy muszą posiadać właściwość, która zwróci liczbę opisującą moc silnika.
  • komponent - jest to nic innego jak realizacja serwisu, tj. konkretyzacja abstrakcji funkcjonalności. W całej aplikacji może istnieć wiele komponentów, np. silnik może być spalinowy lub hybrydowy. Od konfiguracji kontenera zależy jaki komponent zostanie zwrócony dla danego serwisu.
  • zależność zachodzi wtedy jeżeli komponent zależy od innych serwisów. W aplikacji samochodowej zależnością dla komponentu Car jest interfejs IEngine wstrzykiwany przez konstruktor.

Istotnym elementem Castla są klasy rejestrujące serwisy i komponenety. Bez nich kontener nie wie jakich implementacji ma używać w określonym kontekście. Klasa instalatora musi implementować interfejs IWindsorInstaller. W module bazowym naszej aplikacji skonfigurujemy kontener w taki sposób, żeby dla serwisu ICar używał komponentu Car oraz analogicznie dla serwisu IEngine - Engine.

Aplikacja z lotu ptaka

Rozszerzymy więc naszą aplikację o kilka modułów “sprzedażowych” i sprawdzimy jak teraz się zachowa Castle. Schemat solution wraz z wylistowanymi plikami oraz zależności modułowymi są przedstawione na obrazkach poniżej.

Solutionzaleznosci_w_modulach

 

W odniesieniu do zależności pomiędzy projektami, to wszystkie moduły muszą posiadać referencję do kontenera wstrzykiwania zależności.

Dodając zależności użyłem NuGet Package Managera ( jak widać na chwilę obecną aktualna wersja to 3.2.1):
Dodaj_paczke_windsor

Zweryfikujmy w jaki sposób kontener poradzi sobie z rozwiązywaniem zależności w module StandardSeller. Gdy WindsorContainer zostanie już zinstancjonowany, wskazujemy mu skąd ma pobrać rejestracje zależności. Użyta linijka FromAssembly.Containing<ICar> oznacza, że chcemy pobrać wszystkie rejestracje z assembly zawierające interfejs ICar (odkrywcze, co nie ?). Metoda Resolve zwraca aktualnie zarejestrowany komponent dla serwisu ICar. W ten oto sposób na konsoli otrzymamy wartość “100”.

public class CarFactoryWindsorInstaller : IWindsorInstaller
{
	public void Install(IWindsorContainer container, IConfigurationStore store)
	{
		container.Register(Component.For<ICar>().ImplementedBy<Car>());
		container.Register(Component.For<IEngine>().ImplementedBy<Engine>());
	}
}
class Program
{
	static void Main(string[] args)
	{
		var container = new WindsorContainer();
		container.Install(FromAssembly.Containing<ICar>());
		var car = container.Resolve<ICar>();
		Console.WriteLine(car.GetPower());
		Console.ReadKey();
		container.Dispose();
	}
}

W module “SportSeller” wymienimy samochód pozostawiając bez zmian zależność od IEngine. W implementacji SportCar różni się od zwykłego samochodu tym, że moc silnika jest dwa razy większa.

public class SportCar : ICar
{
	private readonly IEngine _engine;

	public SportCar(IEngine engine)
	{
		_engine = engine;
	}

	public int GetPower()
	{
		return _engine.HorsePower * 2;
	}
}
public class SportSellerWindsorInstaller: IWindsorInstaller
{
	public void Install(IWindsorContainer container, IConfigurationStore store)
	{
		container.Register(Component.For<ICar>().ImplementedBy<SportCar>().IsDefault());
	}
}

Warto zwrócić uwagę na dwa ważne szczegóły:

  • konieczne jest poinformowanie Castla, że chcemy używać komponentów z projektu bazowego - CarFactoryWindsorInstaller oraz z modułu sprzedaży samochodów sportowych - SportSellerWindsorInstaller
  • konieczne jest wskazanie, że to własnie klasa SportCar jest domyślnym kompontentem w tym zakresie rozwiązywania zależności tj. w module SportSeller. Realizujemy to poprzez użycie metody IsDefault(). Jeżeli metoda IsDefault() zostanie pominięta, Castle użyje pierwszego zarejestrowanego komponentu tj. Car.

Rezultatem wykonania programu będzie pokazanie na konsoli wartości “200”.

class Program
{
	static void Main(string[] args)
	{
		var container = new WindsorContainer();
		container.Install(new CarFactoryWindsorInstaller());
		container.Install(new SportSellerWindsorInstaller());            
		var car = container.Resolve<ICar>();
		Console.WriteLine(car.GetPower());
		Console.ReadKey();
		container.Dispose();
	}
}

Ostatnim modułem w naszej aplikacji jest moduł sprzedaży samochodów budżetowych - BudgetSeller. W tym przypadku mamy standardowy samochód, z tą różnicą, żę jego elementem składowym jest silnik budżetowy, którego moc jest dwa razy mniejsza nic standardowego silnika. Zwróćmy uwagę, że wskazujemy kontenerowi installator z modułu CarFactory oraz wszystkie rejestracje z aktualnego modułu - FromAssembly.This(). Rezultatem wykonania programu w tym module jest wynik 50.

public class BudgetEngine : IEngine
{
	public int HorsePower
	{
		get { return 50; }
	}
}

public class BudgetSellerWindsorInstaller : IWindsorInstaller
{
	public void Install(IWindsorContainer container, IConfigurationStore store)
	{
		container.Register(Component.For<IEngine>().ImplementedBy<BudgetEngine>().IsDefault());
	}
}

class Program
{
	static void Main(string[] args)
	{
		var container = new WindsorContainer();
		container.Install(new CarFactoryWindsorInstaller());
		container.Install(FromAssembly.This());
		var car = container.Resolve<ICar>();
		Console.WriteLine(car.GetPower());
		Console.ReadKey();
		container.Dispose();
	}
}

Podsumowanie

We wpisie przedstawiłem bardzo podstawowy zarys koncepcji w CastleWindsor oraz przykładową aplikację pokazującą w jaki sposób skonfigurować i używać tego kontenera.

Żródła

http://www.castleproject.org/projects/windsor/

http://docs.castleproject.org/Windsor.MainPage.ashx

http://docs.castleproject.org/Windsor.Screencasts-Podcasts.ashx

 

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.