Wszystkie wpisy, których autorem jest Pellared

Jakość oprogramowania i Extreme Programming

Jakieś pół roku temu miałem przyjemność zakończyć studia podyplomowe z zarządzania projektami na AGH.
Większość zajęć uważam za bardzo pożyteczne, nawet jeśli niektóre mi się nie podobały (np. związane z budżetowaniem), to dały mi dość szeroką wiedzę na temat różnych aspektów związanych z zarządzaniem projektami, jak również organizacjami. Znaczna część kadry pedagogicznej reprezentuje naprawdę wysoki poziom!

Jedną z rzeczy, które zostały mi po studiach jest moja praca dyplomowa (rzadkość na studiach podyplomowych), którą chętnie się podzielę: Jakość a XP. Zapraszam do lektury – komentarze mile widziane! Uwaga: byłem zmuszony zmieścić się z tematem w maksymalnie 32 stronach.

IFactory = abstrakcja na Func<Owned<T>>

Omawiana w poprzednim poście zależność w najprostszej postaci charakteryzuje się pewnym nieprzyjemnym problemem (nie mówiąc o samym fakcie konieczności korzystania z takiej abstrakcji), który postaram się przedstawić na przykładzie.

public interface IFoo
{
	void Save();
}

public class ClassWithResource : Disposable, IFoo
{
	// ...
}

public class ServiceImpl : Disposable, IService
{
	public ServiceImpl(IOwned<IFoo> foo)
	{
		Foo = foo;
	}

	public IOwned<IFoo> Foo { get; private set; }

	public void Add()
	{
		// ...
		Foo.Value.Save();
		// ...
	}

	protected override void DisposeManaged()
    {
        Foo.Dispose();
    }
}

public class ControllerImpl : Disposable, IController
{
	public ControllerImpl(IOwned<IService> service)
	{
		Service = service;
	}

	public IOwned<IService> Service { get; private set; }

	public void HandleRequest()
	{
		// ...
		Service.Value.Add();
		// ...
	}

	protected override void DisposeManaged()
    {
        Service.Dispose();
    }
}

Jak widać korzystanie z „czystego” IOwned poprzez Constructor Injection powoduje, że uzyskujemy łańcuszek zależności IOwned oraz klas implementujących IDisposable – wygląda to co najmniej okropnie. Rozwiązaniem tego problemu jest jak możliwie najszybsze przerwanie tego łańcucha. Najlepiej według mnie to zrobić poprzez połączenia IOwned z wzorcem Factory. Dzięki takiemu podejściu możemy otrzymać następujący kod:

public interface IFoo
{
	void Save();
}

public class ClassWithResource : Disposable, IFoo
{
	// ...
}

public class ServiceImpl : IService
{
	public ServiceImpl(IFactory<IFoo> fooFactory)
	{
		Foo = fooFactory;
	}

	public IFactory<IFoo> FooFactory { get; private set; }

	public void Add()
	{
		// ...
		using (IOwned<IFoo> foo = FooFactory.Create())
		{
			foo.Value.Save();
		}
		// ...
	}
}

public class ControllerImpl : IController
{
	public ControllerImpl(IService service)
	{
		Service = service;
	}

	public IService Service { get; private set; }

	public void HandleRequest()
	{
		// ...
		Service.Add();
		// ...
	}
}

Poniżej przedstawiam sposób w jaki zaimplementowałem IFactory, tak aby można było go zarejestrować w kontenerach, które obsługują wstrzykiwanie Func.

public interface IFactory<out T>
{
	IOwned<T> Create();
}

public interface IFactory<in T, out TOut>
{
	IOwned<TOut> Create(T input);
}

public class Factory<T> : IFactory<T>
{
	private readonly Func<T, IOwned<T>> ownedFactory;
	private readonly Func<T> creator;

	public Factory(Func<T, IOwned<T>> ownedFactory, Func<T> creator)
	{
		this.ownedFactory = ownedFactory;
		this.creator = creator;
	}

	public Factory(Func<T> creator)
		: this(Owned.Create, creator)
	{
	}

	public IOwned<T> Create()
	{
		T ownedInstance = creator();
		IOwned<T> result = ownedFactory(ownedInstance);
		return result;
	}
}

public class Factory<T, TOut> : IFactory<T, TOut>
{
	private readonly Func<TOut, IOwned<TOut>> ownedFactory;
	private readonly Func<T, TOut> creator;

	public Factory(Func<TOut, IOwned<TOut>> ownedFactory, Func<T, TOut> creator)
	{
		this.ownedFactory = ownedFactory;
		this.creator = creator;
	}

	public Factory(Func<T, TOut> creator)
		: this(Owned.Create, creator)
	{
	}

	public IOwned<TOut> Create(T input)
	{
		TOut ownedInstance = creator(input);
		IOwned<TOut> result = ownedFactory(ownedInstance);
		return result;
	}
}

Kod z testami:
Na GitHub w solucji DiProblem.

IOwned = IDisposable + Dependency Injection

Założenie:
Chcemy mieć możliwie wysoką zgodność z SOLID + Depedency Injection

Problem:
1. Piszemy implementację, która zawiera jakieś zasoby i musi je zwalniać (czyli implementacja IDisposable)
2. Omawiane zasoby są bardzo obciążające i nie chcemy je jak najszybciej zwalniać
3. Nie chcemy mieć interfejsu dziedziczącego po IDisposable, bo najczęściej jest to Leaky Abstraction

Rozwiązanie:
Nicholas Blumhardt w poście na temat różnych rodzajów zależności przedstawioną zależność opisuje jako „Owned”, którą jest zaimplementowana np. w Autofac.

Jednak:
1. Nie powinniśmy korzystać z klasy Owned w innych miejscach niż Compostion Root, bo wtedy wszystkie nasze klasy byłby zależne od Autofac’a
2. Może nie mamy AutoFac’a? A może nawet nie używamy kontenera tylko preferujemy Pure DI

Rozwiązanie:
Zaimplementowanie własnej abstrakcji dla tej zależności.

public interface IOwned<out T> : IDisposable
{
    T Value { get; }
}

public class Owned<T> : IOwned<T>
{
    public Owned(T value)
    {
        Value = value;
    }

    public T Value { get; private set; }

    public void Dispose()
    {
        var disposable = Value as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

public static class Owned
{
    public static IOwned<T> Create<T>(T instance)
    {
        return new Owned<T>(instance);
    }
}

Można z podanej implementacji korzystać na dwa sposoby:
1. Zarejestrować w kontenerze
2. Jawne tworzenie w CompositionRoot za pomocą Owned.Create (syntax sugar, aby nie było konieczności podawania generyka)

Implementacja oparta o Autofac’owy Owned:

public class AutofacOwned<T> : IOwned<T>
{
    private readonly Autofac.Features.OwnedInstances.Owned<T> owned;

    public AutofacOwned(Autofac.Features.OwnedInstances.Owned<T> owned)
    {
        this.owned = owned;
    }

    public T Value
    {
        get { return owned.Value; }
    }

    public void Dispose()
    {
        owned.Dispose();
    }
}

Zalety:
Autofac’owy Owned nie zwalnia samego obiektu, ale cały graf związany z obiektem. Czyli jeśli dany obiekt zawierał jakieś referencje na dowolnej głębokości, które implementują IDisposable, to je też zwolni! W przypadku innych kontenerów można łatwo stworzyć implementację IOwned opartą o „child container”. Aby zrobić coś podobnego całkowicie samodzielnie, to mój Owned musiałby być już tak naprawdę kontenerem, który zarządza czasem życia wszystkich obiektów – za trudne :)

Kod z testami:
Na GitHub w solucji DiProblem

Podsumowanie:
To że dana implementacja zarządza jakimiś zasobami i że trzeba po niej posprzątać jest jego cross-cutting concern (problematyka AOP) i nie jest możliwe proste rozwiązanie tego programu w językach zorientowanych obiektowo. Wykorzystane rozwiązanie ma kilka zalet:
1. Nie zmuszamy wszystkich implementacji danego interfejsu, aby implementował Dispose
2. Jesteśmy w stanie sprzątać zasoby jeśli jest taka potrzeba
3. Dzięki dodatkowej abstrakcji i Depedency Injection widziemy po samych sygnaturach, które klasy zawierają zależności, które wymagają posprzątania
ALE NIESTETY:
1. Klasa „kliencka” musi sama wiedzieć, że ma do czynienia z referencją, którą trzeba zwolnić – jak zapomnimy nasz intefejs opakować w IOwned i go zwolnić – to nic nam nie pomoże (no chyba, że napisaliśmy „sprytną” implementację IDisposable albo używamy finalizatorów)
2. Wprowadzamy dodatkową abstrakcję przez co rośnie złożoność kodu

Widziałem też rozwiązanie tego problemu polegającego na próbie rzutowania danej klasy do IDisposable i ew. jego zwolnieniu (przykład). Takie rozwiązanie mi się jednak nie podoba, ponieważ wymaga rzutowania w wielu miejscach aplikacji. Mnie się udało scentralizować rzutowania do klasy Owned.