Skip to content

4. Введення залежностей

Введення залежностей можна розглядати як наслідок інверсії контролю. Проілюструємо це на новому прикладі. Розглянемо такий трирівневий веб-додаток:

Припустимо, що доступ до шару DAO контролюється інтерфейсом [IArticlesDao], який ми розглядали раніше, і що весь додаток є веб-додатком для покупки товарів в Інтернеті. Товари управляються рівнем [Dao]. Припустимо, що доступ до бізнес-рівня контролюється таким інтерфейсом:

Public Interface IArticlesDomain
     ' придбати кошик товарів
    Sub acheter(ByVal panier As Panier)
     ' отримання списку товарів
    Function getAllArticles() As IList
     ' отримати конкретний товар
    Function getArticleById(ByVal idArticle As Integer) As Article
     ' для реєстрації помилок
    ReadOnly Property erreurs() As ArrayList
End Interface

Ми не будемо зупинятися на значенні різних методів. Зазначимо лише, що метод [getAllArticles], який повинен отримати список усіх товарів, що продаються, потребує доступу до даних. Щоб їх отримати, він повинен звернутися до інтерфейсу [IArticlesDao]. Скелет класу, що реалізує інтерфейс [IArticlesDomain], може виглядати так:

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

         'приватні поля
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

         ' конструктор
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

Як ми вже зазначали, деякі методи інтерфейсу бізнес-шару потребують запиту даних до шару [Dao]. Отже, наш клас реалізації [AchatsArticles] інтерфейсу [IArticlesDomain] потребує посилання на реалізацію інтерфейсу [IArticlesDao]. У наведеному вище прикладі таким посиланням є приватне поле [_articlesDao]. Воно надається під час створення об’єкта [AchatsArticles].

Припустимо, що клас [AchatsArticles] вже написано і ми хочемо протестувати його за допомогою тесту типу [Nunit]. Створимо цей тест так, щоб він використовував файл конфігурації Spring:

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

         ' об'єкт, що тестується
        Private articlesDomain As IArticlesDomain

        <SetUp()> _
        Public Sub init()
       ' отримання екземпляра конструктора об’єктів Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' вимагається створення екземпляра об’єкта DAO «articles»
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
...
    End Class

Яким може бути вміст конфігураційного файлу Spring [spring-config-domain.xml]? Він може виглядати так:

<?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>

     <!-- клас реалізації інтерфейсу 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>

     <!-- клас, що реалізує інтерфейс IArticlesDomain -->
  <object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
</objects>

Цей файл — це той самий, що вже використовувався для створення екземпляра синглтона типу [IArticlesDao] з шару [Dao], до якого було додано код для створення екземпляра синглтона типу [IArticlesDomain] з бізнес-шару. Як саме він буде побудований?

  • Зовнішній код запитує у Spring посилання на синглтон із назвою «articlesdomain» у файлі конфігурації. Це стосується методу [init] нашого тестового класу:
        <SetUp()> _
        Public Sub init()
       ' отримуємо екземпляр об'єктного фабрикатора Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-domain.xml", FileMode.Open))
       ' вимагаємо створення екземпляра об’єкта DAO «articles»
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDao)
        End Sub
  • Spring знаходить у своєму конфігураційному файлі визначення відповідного синглтона. Він виявляє, що для його інстанціювання йому потрібен інший синглтон під назвою «articlesdao»:
  <object name="articlesdomain" class="istia.st.articles.domain.AchatsArticles, articlesdomain">
      <constructor-arg index="0">
        <ref object="articlesdao" />
    </constructor-arg>
  </object>
  • Тоді Spring створить екземпляр синглтона «articlesdao». Ми вже пояснювали, як це відбувається.
  • Після цього він може створити екземпляр синглтона «articlesdomain» і передати посилання на нього коду, який його запросив.

Цей механізм можна схематично зобразити так:

Бачимо, що Spring впорався із залежністю синглтона «articlesdomain» від синглтона «articlesdao». Щоб використовувати Spring таким чином, нам довелося створити клас із конструктором, який приймає як аргумент залежний синглтон:

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

         'приватні поля
        Private _articlesDao As IArticlesDao

         ' конструктор
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub
...
    End Class
End Namespace

Термін «ін'єкція залежностей» охоплює одночасно:

  • особливий спосіб побудови класів, що інстанціюються, з урахуванням їхніх залежностей
  • спосіб, у який Spring керує цими залежностями під час створення екземплярів цих класів