Skip to content

4. Wstrzykiwanie zależności

Wstrzykiwanie zależności można uznać za konsekwencję odwrócenia kontroli. Zilustrujemy to na nowym przykładzie. Rozważmy następującą trójwarstwową aplikację internetową:

Załóżmy, że dostęp do warstwy DAO jest kontrolowany przez omówiony wcześniej interfejs [IArticlesDao] oraz że cała aplikacja jest aplikacją internetową służącą do zakupu artykułów w sieci. Artykuły są zarządzane przez warstwę [Dao]. Zakładamy, że dostęp do warstwy biznesowej jest kontrolowany przez następujący interfejs:

Public Interface IArticlesDomain
     ' zakup koszyka z artykułami
    Sub acheter(ByVal panier As Panier)
     ' pobieranie listy artykułów
    Function getAllArticles() As IList
     ' pobieranie konkretnego artykułu
    Function getArticleById(ByVal idArticle As Integer) As Article
     ' w celu zarejestrowania błędów
    ReadOnly Property erreurs() As ArrayList
End Interface

Nie będziemy się tutaj zagłębiać w znaczenie poszczególnych metod. Wystarczy zauważyć, że metoda [getAllArticles], która ma pobrać listę wszystkich artykułów w sprzedaży, potrzebuje dostępu do danych. Aby je uzyskać, musi zwrócić się do interfejsu [IArticlesDao]. Szkielet klasy implementującej interfejs [IArticlesDomain] mógłby wyglądać następująco:

Imports istia.st.articles.dao
...
Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

         'pola prywatne
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

         ' konstruktor
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

Jak już wspomnieliśmy, niektóre metody interfejsu warstwy biznesowej muszą pobierać dane z warstwy [Dao]. Nasza klasa implementacyjna [AchatsArticles] interfejsu [IArticlesDomain] wymaga zatem odwołania do implementacji interfejsu [IArticlesDao]. W powyższym przykładzie odwołaniem tym jest pole prywatne [_articlesDao]. Odwołanie to jest dostarczane w momencie tworzenia obiektu [AchatsArticles].

Załóżmy, że napisano klasę [AchatsArticles] i chcemy ją przetestować za pomocą testu typu [Nunit]. Stwórzmy ten test tak, aby korzystał z pliku konfiguracyjnego Springa:

...
Imports Spring.Objects.Factory.Xml
Imports System.IO
...
    <TestFixture()> _
    Public Class NunitSpringTestArticlesDomain

         ' obiekt do testowania
        Private articlesDomain As IArticlesDomain

        <SetUp()> _
        Public Sub init()
       ' pobieramy instancję konstruktora obiektów Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' wymagane jest utworzenie instancji obiektu DAO „articles”
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
...
    End Class

Jaka byłaby zawartość pliku konfiguracyjnego [spring-config-domain.xml] w Spring? Mógłby on wyglądać następująco:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <description>Gestion d'une table d'articles</description>

     <!-- klasa implementująca interfejs IArticlesDao -->
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
      <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
         <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
         <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>

     <!-- klasa implementująca interfejs IArticlesDomain -->
  <object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
</objects>

Ten plik to ten sam, który został już użyty do instancjonowania singletonu typu [IArticlesDao] z warstwy [Dao], do którego dodano kod służący do instancjonowania singletonu typu [IArticlesDomain] z warstwy biznesowej. W jaki sposób zostanie on skonstruowany?

  • Zewnętrzny kod zwraca się do Springa z prośbą o odwołanie do singletonu o nazwie „articlesdomain” w pliku konfiguracyjnym. Tak jest w przypadku metody [init] naszej klasy testowej:
        <SetUp()> _
        Public Sub init()
       ' pobieramy instancję generatora obiektów Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' żądamy instancjonowania obiektu DAO „articles”
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
  • Spring odnajduje w swoim pliku konfiguracyjnym definicję danego singletonu. Stwierdza, że do utworzenia jego instancji potrzebuje innego singletonu o nazwie „articlesdao”:
  <object name="articlesdomain" class="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
  • Spring utworzy wówczas instancję singletonu „articlesdao”. Wyjaśniliśmy już, w jaki sposób to robi.
  • Po wykonaniu tej czynności Spring może utworzyć instancję singletonu „articlesdomain” i zwrócić odwołanie do niego kodowi, który o to poprosił.

Mechanizm ten można przedstawić schematycznie w następujący sposób:

Widać, że Spring zarządzał zależnością singletonu „articlesdomain” względem singletonu „articlesdao”. Aby korzystać ze Springa w ten sposób, musieliśmy stworzyć klasę z konstruktorem, który jako argument przyjmuje singleton, od którego jest zależna:

Imports istia.st.articles.dao
...
Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

         'pola prywatne
        Private _articlesDao As IArticlesDao

         ' konstruktor
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

Termin „wstrzykiwanie zależności” obejmuje zarówno:

  • specyficzny sposób tworzenia klas przeznaczonych do instancjonowania w oparciu o ich zależności
  • sposób, w jaki Spring zarządza tymi zależnościami w momencie instancjonowania tych klas