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.

5 myśli nt. „IOwned = IDisposable + Dependency Injection”

  1. Pingback: dotnetomaniak.pl
  2. Ja tutaj nie widzę czystej architektury. Proponujesz jakieś dodatkowe, skomplikowane abstrakcje do opakowywania obiektów (ten wpis) lub dziedziczenia po nich (wpis o implementowaniu IDisposable).

    Zacznę od implementacji IDisposable – wzorzec jest prosty, można go znaleźć na MSDN, ten sam od wielu lat. Wystarczy snippet (dla leniwych) i dopisać linie odpowiedzialne za zwolnienie pól, które tego wymagają. Po co dodatkowe dziedziczenie lub kompozycja?

    Zwalnianie obiektów. Sprawa jest prosta – kto tworzy, ten sprząta. Ja tworze obiekt w kodzie? Ja jestem odpowiedzialny za wywołanie Dispose. Jeśli kontener tworzy obiekt, to on powinien go zwalniać. Ponownie, po co dodatkowe opakowywanie za pomocą Owned? Na przykładzie Ninject i ASP.NET MVC:

    public class Foo : IFoo, IDisposable
    {
    // kod…
    }

    public interface IFoo
    {
    // kod…
    }

    Bind<IFoo>().To<Foo>().InRequestScope();

    Kontener tworzy obiekt i sam go zwalnia po skończonym żądaniu (rozpoznaje, że Foo implementuje IDisposable). Mój interfejs jest czysty, bo nie ma IDisposable. Moja klasa jest czysta, bo nie jest opakowana, oraz nie przyjmuje przez konstruktor lub metodę jakiegoś dodatkowego obiektu, który będzie ją czyścić.

    1. Dziękuję za komentarz.

      1. Myślę, że mój post daje wystarczająco dużo powódów, aby używać moijej implementacji. Dodam, że np. programiści Autofac’a też mają swoją implementację Disposable na, której się m.in. wzorowałem; https://github.com/autofac/Autofac/blob/master/Core/Source/Autofac/Util/Disposable.cs. Implementcja IDisposable z MSDN, nie jest bezpieczna wątkowo, nie ma mechanizmu śledzenia zasobów, które zapomnieliśmy zwolnić i jeszcze powoduje duplikację kodu…

      2. Tutaj chodzi o przypadek kiedy musimy zwolnić zasoby szybciej niż to zrobi za nas kontener. Np. żeby nie trzymać niepotrzebnie długo otwartego pliku, którego wymaga klasa Foo. A funkcjonalności tej klasy potrzebujemy tylko, żeby zrobić parę rzeczy. Wydaje mi się, że w typowych webowych aplikacjach biznesowych rzadko występuje taka potrzeba. Gorąco zachęcam do zapoznania się z postem http://nblumhardt.com/2010/01/the-relationship-zoo/ z dodatkowym zwróceniem uwagi na komentarze Marka Seemanna.

  3. Poniżej moje kolejne uwagi do punktu nr 1 :)

    Możliwość wywołania zwalniania obiektów przez kilka wątków na raz, jest już złym pomysłem. Jeżeli istnieje taka sytuacja, to równie dobrze jeden z wątków może używać obiektu, gdy inny wątek już go zwolnił. W .NET BCL też można znaleźć implementacje Dispose, które nie są bezpieczne wielowątkowo. To nie Dispose jest odpowiedzialny za bezpieczeństwo wielowątkowe, ale „coś” wyżej. Może być to osoby wątek, który czeka aż inne wątki przestaną używać obiekt i wtedy on go zwolni. Równie dobrze może być to parent, który tworzy obiekty lub uruchamia wątki. To daje nam pewność, że obiekt zostanie zwolniony w jednym miejscu, w bezpiecznym momencie.

    Wracając do Autofac’a, nie znam wew. implementacji tego konteneru. Być może implementacja Dispose wpasowuje się w ich architekturę. Może akurat takie rozwiązanie jest dobre, bo wprowadzili pewne założenia w innych miejscach, gdzie tworzone są obiekty. Mogę tylko zgadywać :) Akurat argument, że framework A zrobił to tak, to w naszym frameworku B takie rowiązanie też będzie słuszne, nie przemawia do mnie.

    Jeśli chodzi o podstawową implementację IDisposable według MS, to jest tam mechanizm śledzenia zasobów, które zapomnieliśmy zwolnić (czyli po prostu zapomnieliśmy wywołać Dispose). Służy do tego destruktor, który czyści tylko zasoby niezarządzane. Zarządzane obiekty (pola w danej klasie) i tak zostaną wychwycone przez GC.

    Duplikacja kodu? Zgadzam się :)

    1. Z Twoimi komentarzami się zgadzam i najlepiej gdyby wszystko było takie jak to opisałeś :)

      Samo współdzielenie zasobów w programowaniu współbieżnym jest bardzo nieefektywne i niebezpieczne i niewygodne itp itd ale niestety czasami inaczej się nie da, nie mówiąc już o rozproszonym zwalnianiu zasobów (sic!)… Niestety czasami nie mamy innego wyboru , bo może nas zmuszać do tego np. architektura jakiegoś istniejącego już oprogramowania. Zgadzam się, że wtedy najlepszym wyjściem jest posiadanie opisywanego przez Ciebie „parenta”, który zarządza zwalnianiem i zwalnia wtedy kiedy wszystkie „childy” już wykorzystają dany obiekt. Mam już na to przygotowaną implementację, którą podzielę się za 2 posty… Jednak nikomu nie życzę, żeby kiedykolwiek z tego korzystał…

      W pracy mamy do czynienia z dużą ilością „Disposable” – programujemy dość niskopoziomowo jak na .NET :)

      Nie chcemy mieć finalizatorów (destruktory są w C++ :) ) w kodzie produkcyjnym, bo pogorszyłoby nam to wydajność (takie obiekty z automaty lecą do drugiej kolekcji). Generalnie nie mam nic przeciwko finalizatorom – w 99% przypadkach ich narzut nie stanowi problemu. Notabene właśnie do tego mechanizmu śledzenia zamian wykorzystuję destruktor – jakbyś nie zauważył :)

      Bardzo dziękuję za komentarz!

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *


5 − pięć =

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>