Wstrzykiwanie zależności

Uwaga

Ta książka elektroniczna została opublikowana wiosną 2017 r. i od tego czasu nie została zaktualizowana. Jest wiele w książce, która pozostaje cenna, ale niektóre z materiałów są przestarzałe.

Zazwyczaj konstruktor klasy jest wywoływany podczas tworzenia wystąpienia obiektu, a wszystkie wartości, których potrzebuje obiekt, są przekazywane jako argumenty do konstruktora. Jest to przykład wstrzykiwania zależności, a w szczególności jest znany jako wstrzykiwanie konstruktora. Zależności wymagane przez obiekt są wstrzykiwane do konstruktora.

Określając zależności jako typy interfejsów, iniekcja zależności umożliwia oddzielenie konkretnych typów od kodu, który zależy od tych typów. Zazwyczaj używa kontenera, który zawiera listę rejestracji i mapowań między interfejsami i typami abstrakcyjnymi oraz konkretne typy, które implementują lub rozszerzają te typy.

Istnieją również inne typy wstrzykiwania zależności, takie jak wstrzykiwanie setter właściwości i wstrzykiwanie wywołań metody, ale są one mniej często spotykane. W związku z tym w tym rozdziale skupimy się wyłącznie na wykonywaniu wstrzykiwania konstruktora z kontenerem iniekcji zależności.

Wprowadzenie do wstrzykiwania zależności

Wstrzykiwanie zależności jest wyspecjalizowaną wersją wzorca Inversion of Control (IoC), gdzie problemem jest proces uzyskiwania wymaganej zależności. W przypadku iniekcji zależności inna klasa jest odpowiedzialna za wstrzykiwanie zależności do obiektu w czasie wykonywania. Poniższy przykład kodu pokazuje, jak ProfileViewModel klasa jest ustrukturyzowana podczas korzystania z iniekcji zależności:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

    public ProfileViewModel(IOrderService orderService)  
    {  
        _orderService = orderService;  
    }  
    ...  
}

Konstruktor ProfileViewModel odbiera IOrderService wystąpienie jako argument, wstrzykiwany przez inną klasę. Jedyną zależnością ProfileViewModel w klasie jest typ interfejsu. ProfileViewModel W związku z tym klasa nie ma żadnej wiedzy na temat klasy odpowiedzialnej za utworzenie wystąpienia IOrderService obiektu. Klasa, która jest odpowiedzialna za utworzenie wystąpienia IOrderService obiektu i wstawienie go do ProfileViewModel klasy, jest znana jako kontener wstrzykiwania zależności.

Kontenery iniekcji zależności zmniejszają sprzężenie między obiektami, zapewniając obiekt do tworzenia wystąpień klas i zarządzania ich okresem istnienia na podstawie konfiguracji kontenera. Podczas tworzenia obiektów kontener wprowadza do niego wszelkie zależności wymagane przez obiekt. Jeśli te zależności nie zostały jeszcze utworzone, kontener tworzy i rozwiązuje swoje zależności jako pierwsze.

Uwaga

Wstrzykiwanie zależności można również zaimplementować ręcznie przy użyciu fabryk. Jednak użycie kontenera zapewnia dodatkowe możliwości, takie jak zarządzanie okresem istnienia i rejestracja za pośrednictwem skanowania zestawów.

Korzystanie z kontenera wstrzykiwania zależności ma kilka zalet:

  • Kontener usuwa potrzebę zlokalizowania jego zależności i zarządzania okresami istnienia klasy.
  • Kontener umożliwia mapowanie wdrożonych zależności bez wpływu na klasę.
  • Kontener ułatwia testowanie, umożliwiając pozorowanie zależności.
  • Kontener zwiększa łatwość konserwacji, umożliwiając łatwe dodawanie nowych klas do aplikacji.

W kontekście aplikacji korzystającej Xamarin.Forms z maszyny MVVM kontener wstrzykiwania zależności będzie zwykle używany do rejestrowania i rozpoznawania modeli widoków oraz rejestrowania usług i wstrzykiwania ich do modeli widoków.

Dostępnych jest wiele kontenerów iniekcji zależności, z aplikacją mobilną eShopOnContainers przy użyciu narzędzia TinyIoC do zarządzania wystąpieniem klas modelu widoku i usług w aplikacji. Funkcja TinyIoC została wybrana po ocenie wielu różnych kontenerów i oferuje lepszą wydajność na platformach mobilnych w porównaniu z większością znanych kontenerów. Ułatwia tworzenie luźno powiązanych aplikacji i udostępnia wszystkie funkcje powszechnie spotykane w kontenerach iniekcji zależności, w tym metody rejestrowania mapowań typów, rozpoznawania obiektów, zarządzania okresami istnienia obiektów i wstrzykiwania obiektów zależnych do konstruktorów rozpoznawanych przez nią obiektów. Aby uzyskać więcej informacji na temat narzędzia TinyIoC, zobacz TinyIoC w github.com.

W usłudze TinyIoCContainer TinyIoC typ zapewnia kontener wstrzykiwania zależności. Rysunek 3–1 przedstawia zależności podczas korzystania z tego kontenera IOrderService , który tworzy wystąpienie obiektu i wprowadza go do ProfileViewModel klasy.

Dependencies example when using dependency injection

Rysunek 3–1. Zależności podczas korzystania z wstrzykiwania zależności

W czasie wykonywania kontener musi wiedzieć, która implementacja interfejsu IOrderService powinna zostać utworzone, zanim będzie mógł utworzyć wystąpienie ProfileViewModel obiektu. Ten proces obejmuje następujące czynności:

  • Kontener decyduje o tym, jak utworzyć wystąpienie obiektu, który implementuje IOrderService interfejs. Jest to nazywane rejestracją.
  • Kontener tworzący wystąpienie obiektu, który implementuje IOrderService interfejs i ProfileViewModel obiekt. Jest to nazywane rozwiązaniem.

W końcu aplikacja zakończy korzystanie z ProfileViewModel obiektu i stanie się dostępna do odzyskiwania pamięci. W tym momencie moduł odśmiecywania pamięci powinien usunąć wystąpienie, jeśli inne klasy nie współużytkują IOrderService tego samego wystąpienia.

Napiwek

Pisanie kodu niezależnego od kontenera. Zawsze próbuj napisać kod niezależny od kontenera, aby usunąć aplikację z używanego kontenera zależności.

Rejestracja

Przed wstrzyknięciem zależności do obiektu należy najpierw zarejestrować typy zależności w kontenerze. Rejestrowanie typu zwykle polega na przekazaniu kontenera interfejsu i konkretnego typu, który implementuje interfejs.

Istnieją dwa sposoby rejestrowania typów i obiektów w kontenerze za pomocą kodu:

  • Zarejestruj typ lub mapowanie w kontenerze. W razie potrzeby kontener utworzy wystąpienie określonego typu.
  • Zarejestruj istniejący obiekt w kontenerze jako pojedynczy obiekt. W razie potrzeby kontener zwróci odwołanie do istniejącego obiektu.

Napiwek

Kontenery wstrzykiwania zależności nie zawsze są odpowiednie. Wstrzykiwanie zależności wprowadza dodatkową złożoność i wymagania, które mogą nie być odpowiednie lub przydatne dla małych aplikacji. Jeśli klasa nie ma żadnych zależności lub nie jest zależnością dla innych typów, może nie mieć sensu umieścić jej w kontenerze. Ponadto jeśli klasa ma jeden zestaw zależności, które są integralną częścią typu i nigdy się nie zmieni, może nie mieć sensu umieścić go w kontenerze.

Rejestracja typów wymagających wstrzykiwania zależności powinna być wykonywana w jednej metodzie w aplikacji, a ta metoda powinna być wywoływana na wczesnym etapie cyklu życia aplikacji, aby upewnić się, że aplikacja jest świadoma zależności między jej klasami. W aplikacji mobilnej eShopOnContainers jest to wykonywane przez ViewModelLocator klasę, która kompiluje TinyIoCContainer obiekt i jest jedyną klasą w aplikacji, która zawiera odwołanie do tego obiektu. Poniższy przykład kodu pokazuje, jak aplikacja mobilna eShopOnContainers deklaruje TinyIoCContainer obiekt w ViewModelLocator klasie:

private static TinyIoCContainer _container;

Typy są rejestrowane w konstruktorze ViewModelLocator . Jest to osiągane przez najpierw utworzenie TinyIoCContainer wystąpienia, które pokazano w poniższym przykładzie kodu:

_container = new TinyIoCContainer();

Typy są następnie rejestrowane w TinyIoCContainer obiekcie, a poniższy przykład kodu pokazuje najbardziej typową formę rejestracji typów:

_container.Register<IRequestProvider, RequestProvider>();

Metoda przedstawiona Register tutaj mapuje typ interfejsu na konkretny typ. Domyślnie każda rejestracja interfejsu jest skonfigurowana jako pojedyncza, tak aby każdy obiekt zależny odbierał to samo wystąpienie udostępnione. W związku z tym w kontenerze będzie istnieć tylko jedno RequestProvider wystąpienie, które jest współużytkowane przez obiekty, które wymagają wstrzyknięcia IRequestProvider obiektu za pośrednictwem konstruktora.

Typy betonowe można również zarejestrować bezpośrednio bez mapowania z typu interfejsu, jak pokazano w poniższym przykładzie kodu:

_container.Register<ProfileViewModel>();

Domyślnie każda konkretna rejestracja klas jest skonfigurowana jako wystąpienie wieloaktywne, tak aby każdy obiekt zależny odbierał nowe wystąpienie. W związku z tym po rozwiązaniu ProfileViewModel problem zostanie utworzone nowe wystąpienie, a kontener wstrzykuje jego wymagane zależności.

Rozwiązanie

Po zarejestrowaniu typu można go rozpoznać lub wprowadzić jako zależność. Gdy typ jest rozpoznawany i kontener musi utworzyć nowe wystąpienie, wprowadza wszystkie zależności do wystąpienia.

Ogólnie rzecz biorąc, gdy typ jest rozpoznawany, występuje jedna z trzech rzeczy:

  1. Jeśli typ nie został zarejestrowany, kontener zgłasza wyjątek.
  2. Jeśli typ został zarejestrowany jako pojedynczy, kontener zwraca pojedyncze wystąpienie. Jeśli ten typ jest wywoływany po raz pierwszy, kontener tworzy go, jeśli jest to wymagane, i przechowuje odwołanie do niego.
  3. Jeśli typ nie został zarejestrowany jako pojedynczy, kontener zwraca nowe wystąpienie i nie zachowuje odwołania do niego.

Poniższy przykład kodu pokazuje, jak RequestProvider można rozpoznać typ, który został wcześniej zarejestrowany w usłudze TinyIoC:

var requestProvider = _container.Resolve<IRequestProvider>();

W tym przykładzie program TinyIoC jest proszony o rozwiązanie konkretnego typu dla IRequestProvider typu wraz z wszelkimi zależnościami. Zazwyczaj metoda jest wywoływana Resolve , gdy wymagane jest wystąpienie określonego typu. Aby uzyskać informacje na temat kontrolowania okresu istnienia rozpoznanych obiektów, zobacz Zarządzanie okresem istnienia rozwiązanych obiektów.

Poniższy przykład kodu pokazuje, jak aplikacja mobilna eShopOnContainers tworzy wystąpienia typów modeli widoku i ich zależności:

var viewModel = _container.Resolve(viewModelType);

W tym przykładzie usługa TinyIoC zostanie poproszona o rozwiązanie problemu typu modelu widoku dla żądanego modelu widoku, a kontener rozwiąże również wszelkie zależności. Podczas rozpoznawania ProfileViewModel typu zależności do rozpoznania są ISettingsService obiektem i obiektem IOrderService . Ponieważ rejestracje interfejsu były używane podczas rejestrowania SettingsService klas i, TinyIoC zwraca pojedyncze wystąpienia dla SettingsService klas iOrderService, OrderService a następnie przekazuje je do konstruktora ProfileViewModel klasy . Aby uzyskać więcej informacji o tym, jak aplikacja mobilna eShopOnContainers tworzy modele widoku i kojarzy je z widokami, zobacz Automatyczne tworzenie modelu widoku za pomocą lokalizatora modelu widoku.

Uwaga

Rejestrowanie i rozpoznawanie typów w kontenerze ma koszt wydajności ze względu na użycie odbicia kontenera do tworzenia każdego typu, zwłaszcza jeśli zależności są odtwarzane dla każdej nawigacji stron w aplikacji. Jeśli istnieje wiele lub głębokie zależności, koszt tworzenia może znacznie wzrosnąć.

Zarządzanie okresem istnienia rozwiązanych obiektów

Po zarejestrowaniu typu przy użyciu rejestracji klasy betonowej domyślne zachowanie tinyIoC polega na utworzeniu nowego wystąpienia zarejestrowanego typu za każdym razem, gdy typ jest rozpoznawany, lub gdy mechanizm zależności wprowadza wystąpienia do innych klas. W tym scenariuszu kontener nie przechowuje odwołania do rozpoznanego obiektu. Jednak podczas rejestrowania typu przy użyciu rejestracji interfejsu domyślne zachowanie programu TinyIoC polega na zarządzaniu okresem istnienia obiektu jako pojedynczego obiektu. W związku z tym wystąpienie pozostaje w zakresie, gdy kontener jest w zakresie i jest usuwane, gdy kontener wykracza poza zakres i jest wyrzucany bezużytecznie, lub gdy kod jawnie usuwa kontener.

Domyślne zachowanie rejestracji tinyIoC można zastąpić przy użyciu płynnych AsSingleton metod i AsMultiInstance interfejsu API. Na przykład metodę AsSingleton można użyć z Register metodą , aby kontener tworzy lub zwracał pojedyncze wystąpienie typu podczas wywoływania Resolve metody. W poniższym przykładzie kodu pokazano, jak program TinyIoC jest poinstruowany o utworzeniu LoginViewModel pojedynczego wystąpienia klasy:

_container.Register<LoginViewModel>().AsSingleton();

Przy pierwszym rozpoznaniu LoginViewModel typu kontener tworzy nowy LoginViewModel obiekt i utrzymuje do niego odwołanie. W przypadku każdej kolejnej LoginViewModelrozdzielczości kontener zwraca odwołanie do LoginViewModel obiektu, który został wcześniej utworzony.

Uwaga

Typy zarejestrowane jako singletony są usuwane po usunięciu kontenera.

Podsumowanie

Wstrzykiwanie zależności umożliwia oddzielenie konkretnych typów od kodu, który zależy od tych typów. Zazwyczaj używa kontenera, który zawiera listę rejestracji i mapowań między interfejsami i typami abstrakcyjnymi oraz konkretne typy, które implementują lub rozszerzają te typy.

TinyIoC to lekki kontener, który oferuje lepszą wydajność na platformach mobilnych w porównaniu z większością znanych kontenerów. Ułatwia tworzenie luźno powiązanych aplikacji i udostępnia wszystkie funkcje powszechnie spotykane w kontenerach iniekcji zależności, w tym metody rejestrowania mapowań typów, rozpoznawania obiektów, zarządzania okresami istnienia obiektów i wstrzykiwania obiektów zależnych do konstruktorów rozpoznawanych obiektów.