Zrozumienie zagadnień związanych z DI

0

Generalnie jestem takim programistą z doskoku, który wskoczył na to stanowisko w ramach awansu w strukturze organizacyjnej firmy i wszystkiego co się uczę to głównie już na istniejących przykładach. Rzadko mam okazję poznać tajniki konfiguracji danego projektu gdyż najczęściej słyszę "powiel to co już skonfigurowane i rób tak aby zdążyć w określonym czasie".
Można tak pracować, ale nie lubię być nieświadomy tego z czym niektóre rzeczy się biorą.
Do tej pory do wstrzykiwania klas implementujących danych interfejs używałem już skonfigurowanego w projektach Windsora.
Jak chciałem dodać jakąś zależność, którą chciałem wykorzystać to w odpowiedniej klasie po prostu dodawałem np.:

container.Register(Component.For<DbContext>()
                .ImplementedBy<IdbContext>()
                .LifestylePerWcfOperation());

Mam konkretne pytania:

  1. Czym jest kontener? Jak to łopatologicznie rozgryźć?
  2. Czemu niektóre interfejsy w projekcie są wstrzykiwane w ramach jednego kontenera, a w innym przypadku każdy interfejs to rejestracja oddzielnego kontenera?
    Np.
//Interfejsy w ramach jednego kontenera
public class ServicesInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container)
        {
            container
                .Register(
                    Component.For<IFirstService>()
                        .ImplementedBy<FirstService>()
                        .LifeStyle.PerWcfOperation()
                )
                .Register(
                    Component.For<ISecondService>()
                        .ImplementedBy<SecondService>()
                        .LifeStyle.PerWcfOperation()
                )
                .Register(
                    Component.For<IThirdService>()
                        .ImplementedBy<ThirdService>()
                        .LifeStyle.PerWcfOperation()
                )
        }
    }
//Każdy interfejs w innym kontenerze
public class DataInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<DbContext>()
                .ImplementedBy<IdbContext>()
                .LifestylePerWcfOperation());

            container.Register(Component.For<IDbContextAdapter>()
                .ImplementedBy<DbContextAdapter>()
                .LifestylePerWcfOperation());

            container.Register(Component.For<ITransactionFactory>()
                .ImplementedBy<TransactionFactory>()
                .LifestylePerWcfOperation());

            container.Register(Component.For<ISpExecutor>()
                .ImplementedBy<SpExecutor>()
                .LifestylePerWcfOperation());
        }
    }
  1. W niektórych klasach Windsora spotykam też AddFacility(). Do czego to służy?
0

W tym przypadku myślę że container to zbiór pewnych serwisów. A rozdzielanie nich to już grupa serwisów(pkt 2 i tak pewnie do jednego wora ale pogrupowane przez ImplementedBy? pewnie nie ma znaczenia skąd są rejestrowane ważne dlaczego). AddFacility może służyć do dodania serwisu w trakcie działania programu, wykonany albo przetworzenia danego obiektu w ramach serwisu(to jest strzał, nie podałeś nawet argumentów metody).

3
altek napisał(a):

Mam konkretne pytania:

  1. Czym jest kontener? Jak to łopatologicznie rozgryźć?

To taki centralny w projekcie rejestr typów, sposobów tworzenia ich instancji oraz czasu ich życia. Generalnie używanie kontenera składa się z dwóch faz:
a. Wrzucanie, czy raczej mądrzej rejestrowanie, od tego masz nawet metodę Register.
b. Wyciąganie - zazwyczaj metodą w rodzaju Resolve albo Get, ale to nieważne, bo generalnie chodzi o to, aby działo się to automatycznie.

Dzięki użyciu kontenera nie trzeba tworzyć skomplikowanych grafów obiektów ręcznie. Np. gdy kontroler zależy od kilku serwisów, każdy z nich od kilku helperów, wszędzie potrzebny jakiś dbcontext, to trzeba by użyć kilkanaście wywołań new, ale przy okazji pamiętać, aby dbContext był tylko jeden. A potem, gdy któryś z serwisów dostanie nową zależność, to trzeba edytować wszystkie miejsca, gdzie jest używany i to nowe new dopisać. Kontener ten problem rozwiązuje - w konstruktorze piszesz tylko, jakiej zależności potrzebujesz, resztą się zajmie kontener.

  1. Czemu niektóre interfejsy w projekcie są wstrzykiwane w ramach jednego kontenera, a w innym przypadku każdy interfejs to rejestracja oddzielnego kontenera?

To są dwa równoważne zapisy, po prostu w jednym metody są wywołane łańcuchowo, a w drugim nie. Przyjrzyj się - w obu przypadkach jest tylko jedna zmienna container. Semantycznie to nie ma znaczenia, to kwestia tylko stylu zapisu.

  1. W niektórych klasach Windsora spotykam też AddFacility(). Do czego to służy?

To takie jakby wtyczki do Windsora, różnego rodzaju rozszerzenia, które usprawniają rejestrowanie/wyciąganie obiektów. Np. zamiast jakiejś żmudnej, manualnej konfiguracji jest jakieś facility, które np. magicznie tworzy fabryki dla jakichś tam typów. Albo bardziej życiowy przykład - integracja z ASP.NET MVC. Goły framework, nie wspiera kontrolerów z parametryzowanych konstruktorami. Jeśli napiszesz konstruktor przyjmujący jakąś zależność, to taka aplikacja nie wystartuje, od razu wyrzuci wyjątek. Aby to zadziałało, trzeba podmienić standardowe ControllerFactory na takie, które umie wyciągać zarejestrowane w Windsorze zależności i tworzyć poprawne kontrolery. No i istnieje na pewno już takie facility, które taką integrację zapewnia. Do WCFa na pewno też jest, więc pewnie sam nieświadomie takiego facility używasz.

0

Zapoznaję się obecnie z Autofac. I o ile ogarnąłem podstawowy zakres i rejestrację jeden interfejs - jedna klasa to trafiłem na taki artykuł LINK. Ściągnałem również omawiane rozwiązanie z Git huba.
Są tam omawiane moduły Autofaca.

I przykładowo jest taki moduł:

public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(typeof(DefaultModule).Assembly)
            .AsImplementedInterfaces()
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(LoggerInterceptor));
    }
}

Nie do końca wiem co tu się dzieje. Początkowo myślałem, że rejestracja szuka wszystkich klas typu "DefaultModule" czyli np. takich, które po niej dziedziczą, ale w projekcie z githuba nie ma żadnej klasy, która dziedziczy po DefaultModule.
Mógłby mi ktoś wytłumaczyć co tu się dzieje krok po kroku (tj. wewnątrz metody Load?

2

@altek:

Jeżeli się nie mylę, to RegisterAssemblyTypes() po prostu przeszukuje podane Assembly (chyba m.in. na podstawie konwencji), które w tym przypadku jest Assembly tego DefaultModule

typeof(DefaultModule).Assembly

weź typ DefaultModule, a następnie jego Assembly

...i je przeszukaj.

Czyli właściwie przeszukujesz "to" Assembly, bo public class DefaultModule

2

Równie dobrze można byłoby napisać ThisAssembly.
Generalnie to jest moduł AutoFaca, a ten kod robi dokładnie to, co jest tam po angielsku napisane:

  1. Bierze wszystkie typy z assembly, w którym znajduje się klasa DefaultModule.
  2. Każdy typ rejestruje dla wszystkich interfejsów, które implementuje.
  3. I dla każdego tak zarejestrowanego typu włącza interceptor LoggerInterceptor.

Moduł to taki "kawałek rejestracji", przydaje się po to, żeby nie mieć wszystkich rejestracji w jednym pliku.
Interceptor to takie proxy, wywołania metod klasy najpierw przechodzą przez interceptor zanim dojdą do właściwej klasy. Jak można sądzić po nazwie LoggerInterceptor pewnie zajmuje się jakimś logowaniem, np. argumentów oraz wyników metod.

0

Praktycznie wszystko jest już dla mnie jasne.
Jednak zastanawia mnie tylko jedna rzecz...

somekind napisał(a):
  1. Każdy typ rejestruje dla wszystkich interfejsów, które implementuje.

A gdy są różne typy tj. klasy, które implementują ten sam interfejs?
Jak sobie wtedy Autofac z tym poradzi? Czy może nie poradzi?

Poniżej przykład takiego zagadnienia.

Mamy sobie klasy A, B, C, D.
Klasa A, B, C implementuje interfejs I.
Do klasy D w konstruktorze wstrzykujemy zależność dla interfejsu I i w zamyśle chcemy użyć metod klasy C, a nie pozostałych, które implementują ten interfejs.

1

Zgaduję, że albo wykryje taką sytuację na etapie rejestracji i już wtedy rzuci wyjątek, albo stanie się to dopiero na etapie tworzenia instancji klasy D. Pewności nie mam, bo nie piszę kodu w taki sposób.

W Autofacu można definiować sposoby tworzenia określonych typów, więc można też zmusić go do użycia określonej klasy. Mniej więcej tak:

builder.Register(c => new D(c.Resolve<C>()));

Tylko... skoro chcesz, aby D używało C, nie prościej po prostu mieć konstruktor, który przyjmuje C? No bo skoro D zawsze musi użyć C, a nie może innych typów implementujących ten interfejs, to robienie zależności na interfejs wygląda nieprawidłowo.

0

Słuchajcie...
Spotkałem się ostatnio z takim stwierdzeniem, że do normalnego DI wystarczy to standardowe, które jest w .NET Core.
Natomiast do wstrzykiwania kontrolerów przydatny jest Autofac. O co chodzi z tym wstrzykiwaniem kontrolerów? Jaki jest tego cel i kiedy to się stosuje?

0

Do normalnego DI wystarczy konstruktor.

A problem z kontrolerami występuje wtedy, gdy kontroler ma parametryzowany konstruktor, bo framework domyślnie takiej sytuacji nie wspiera, więc trzeba mieć odpowiednią wtyczkę integrującą z kontenerem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1