Главная страница
Навигация по странице:

  • . Какую функциональность мы теряем

  • Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей


    Скачать 5.66 Mb.
    НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
    АнкорВнедрение зависимостей в .net
    Дата14.12.2019
    Размер5.66 Mb.
    Формат файлаpdf
    Имя файлаВнедрение зависимостей в .NET.pdf
    ТипРуководство
    #100226
    страница30 из 43
    1   ...   26   27   28   29   30   31   32   33   ...   43
    Registry или C onfi gurati onExpressi on?
    Несмотря на то, что большинство конфигурационных API (например, методы
    For и
    Scan
    ) все равно доступны при прямом наследовании от
    Registry
    , мы не можем использовать методы, определенные непосредственно для класса
    ConfigurationExpression

    . Какую функциональность мы теряем?
    Существует только пять методов, которые определены непосредственно для
    ConfigurationExpression
    , и разделяются они на две категории:

    Методы, которые считывают конфигурацию из XML

    Методы, добавляющие регистры
    Скорее всего, нам не понадобится добавлять
    Registry внутри
    Registry
    , поэтому данная возможность не столь важна.
    Конфигурация, определенная в XML, – совершенно другой способ выражения конфигурации. Мы выражаем определенную часть конфигурации приложения либо в
    XML, либо при помощи конфигурационного API, но использовать эти два способа одновременно нельзя. В этом свете невозможность определения источников XML из
    Registry не является большим недостатком.
    И все-таки, можно ли унаследовать регистр от
    ConfigurationExpression вместо того, чтобы наследовать его напрямую от
    Registry
    ? К несчастью, мы не можем это сделать, поскольку конструктор
    ConfigurationExpression
    – внутренний.
    Основной момент – это то, что регистр не может быть унаследован от
    ConfigurationExpression
    , а должен наследоваться от самого
    Registry
    Класс
    MenuRegistry наследуется от
    Registry и определяет всю конфигурацию в конструкторе. Внутри класса вы можете получить доступ ко всему открытому API класса
    Registry
    , поэтому использовать методы
    For и
    Scan вы можете тем же самым способом, что и в разделе 11.1.2 "Конфигурирование контейнера". Единственное отличие заключается в том, что в данном случае вы реализуете не безымянный делегат, а конструктор. Вместо блока кода и вездесущей переменной r
    , к которым вы, возможно, на данный момент уже привыкли, вы обращаетесь к API посредством переменной this

    424
    После получения
    MenuRegistry вы теперь можете добавить его в контейнер с помощью метода
    Configure
    : container.Configure(r => r.AddRegistry());
    Эта generic-версия метода
    AddRegistry требует, чтобы реализация
    Registry имела конструктор по умолчанию, но помимо этого доступна и не generic-перегрузка, которая принимает экземпляр
    Registry в качестве входного параметра, предоставляя нам полный контроль над тем, как он создается.
    П римечание
    Методы
    AddRegistry
    – два из пяти методов, определенных непосредственно для
    ConfigurationExpression и недоступных внутри
    Registry
    Вы также можете передать
    Registry непосредственно через конструктор контейнера: var container = new Container(new MenuRegistry());
    Я предпочитаю использовать метод
    Configure
    , поскольку он позволяет мне добавлять в последовательност ь более одного регистра.
    П одсказка
    Регистры позволяют вам упаковать и структурировать код конфигурации вашего контейнера. Используйте их вместо однострочной конфигурации, так как это сделает вашу Composition Root более читабельной.
    Благодаря регистрам мы можем конфигурировать StructureMap, используя в качестве конфигурации код или автоматическую регистрацию, тогда как XML конфигурация должна импортироваться непосредственно через метод
    Configure
    . Кроме того, мы можем сочетать оба подхода, получая некоторую часть конфигурации из XML, а остальную – из одного или более чем одного регистра: container.Configure(r =>
    { r.AddConfigurationFromXmlFile(configName); r.AddRegistry();
    });
    После того, как контейнер сконфигурирован, вы можете начать разрешать с помощью него сервисы, как это описано в разделе 11.1.1 "Разрешение объектов".
    Данная глава познакомила вас с DI-контейнером StructureMap и продемонстрировала фундаментальные механизмы: как конфигурировать контейнер и впоследствии использовать его для разрешения сервисов. Разрешение сервисов с легкостью выполняется при помощи единичного вызова метода
    GetInstance
    , поэтому вся сложность заключается в конфигурировании контейнера. Это можно сделать несколькими различными способами, включая императивный код и XML. До настоящего момента мы рассматривали только самое основное API, поэтому нам придется рассмотреть еще и более продвинутые API. Один из самых важных вопросов – как управлять жизненным циклом компонентов.

    425 11.2. Управление жизненным циклом
    В главе 8 "Жизненный цикл объектов" мы обсуждали процесс управления жизненным циклом, включая самые универсальные, концептуальные стили существования, например,
    Singleton и T ransient . StructureMap поддерживает множество различных стилей существования и позволяет нам конфигурировать жизненные циклы всех сервисов. Стили существования, продемонстрированные в таблице 11-2, доступны в виде составляющей
    API StructureMap.
    Таблица 11-2: Стили существования StructureMap
    1. Название
    2. Комментарии
    3. PerRequest
    4. Название стиля существования Per Graph, используемое в рамках StructureMap. Этот стиль используется им по умолчанию. Экземпляр ы контейнером не отслеживаются.
    5. Singleton
    6. Стандартный Singleton
    7. HttpCont ext
    8. Название стиля существования Web Request Context, используемое в рамках StructureMap.
    9. T hreadLocal
    10. На один поток создается один экземпляр.
    11. Hybrid
    12. Комбинация HttpCont ext и T hreadLocal. HttpContext используется тогда, когда он доступен (например, когда контейнер размещается в веб-приложении), а
    T hreadLocal используется в качестве резерва.
    13. HttpSession
    14. На одну HT TP-сессию создается один экземпляр.
    Используйте его с осторожностью.
    15. HybridHttpSession
    16. Комбинация HttpSession и T hreadLocal. HttpSession используется тогда, когда он доступен (например, когда контейнер размещается в веб-приложении), а
    T hreadLocal используется в качестве резерва.
    17. Unique
    18. Название стиля существования T ransient, используемое в рамках StructureMap.
    Реализации различных стилей существования в StructureMap эквивалентны основным паттернам стилей существования, описанным в главе 8, поэтому в этой главе я не буду много времени тратить на их обсуждение.
    П одсказка
    Стиль существования, используемый в StructureMap по умолчанию, – это Per Graph. Как мы уже обсуждали в разделе 8.3.3 "Per Graph", данный стиль предлагает наилучший баланс между эффективностью и безопасностью. Кроме того, если ваши сервисы потоко- безопасны, то наиболее эффективный стиль существования в этом случае – Singleton, но при этом вы должны не забывать конфигурировать такие сервисы.
    В данном разделе вы увидите, как определять стили существования для компонентов и в коде, и в XML. Помимо этого в качестве более продвинутого сценария вы рассмотрите то, как реализовать пользовательский стиль существования с целью продемонстрировать, что ваши возможности не ограничены встроенными в StructureMap стилями существования.

    426
    После прочтения этого раздела вы должны будете уметь использовать стили существования StructureMap в своем собственном приложении.
    Давайте начнем с обзора того, как конфигурировать стили существования для компонентов.
    Конфигурирование стилей существования
    В данном разделе мы приведем обзор того, как управлять жизненными стилями компонентов с помощью StructureMap. Стили существования конфигурируются в виде составляющей части конфигурируемых компонентов, и вы можете определить их как в коде, так и в XML. Мы поочереди рассмотрим каждый из этих стилей существования.
    Конфигурирова ние стилей суще ствования с помощью кода
    Стили существования конфигурируются в виде составляющей части Configure API, которое вы используете для конфигурирования компонентов в общем. Это столь же просто, как и приведенный ниже код: container.Configure(r => r.For().Singleton());
    Данный код конфигурирует конкретный класс
    SauceBéarnaise в виде Singleton таким образом, что каждый раз, когда запрашивается
    SauceBéarnaise
    , создается один и тот же экземпляр. Если вы хотите преобразовать абстракцию в конкретный класс с заданным жизненным циклом, то объявление стиля существования помещается между вызовами методов
    For и
    Use
    : container.Configure(r => r.For().Singleton().Use());
    Данный код преобразует
    IIngredient в
    SauceBéarnaise
    , а также конфигурирует его в виде
    Singleton
    . Существуют и другие методы, аналогичные методу
    Singleton
    , которые позволяют нам объявить множество других стилей существования. Но не все стили существования обладают таким методом. Все стили существования могут конфигурироваться при помощи универсального метода
    LifecycleIs
    . К примеру, стиль существования
    Unique не имеет такого метода, но может быть сконфигурирован следующим образом: container.Configure(r => r
    .For()
    .LifecycleIs(new UniquePerRequestLifecycle()));
    Метод
    LifecycleIs принимает в качестве параметра экземпляр
    ILifecycle
    , поэтому вы можете передать его в любой класс, реализующий этот интерфейс. Как вы увидите в разделе 11.2.2 "Разработка пользовательского стиля существования", таким же способом мы конфигурируем компонент, имеющий пользовательский жизненный цикл.
    Все встроенные в StructureMap стили существования обладают соответствующей реализацией
    ILifecycle
    , за исключением используемого по умолчанию стиля существования Per Graph. Этот стиль существования обычно неявным образом конфигурируется за счет опускания явного стиля существования. Во всех конфигурациях,

    427 которые вы видели в разделе 11.1 "Знакомство со StructureMap", использовался стиль существования Per Graph.
    П одсказка
    Опускание объявления стиля существования подразумевает Per Graph, который используется в StructureMap по умолчанию. Но null на месте экземпляра
    ILifecycle также подразумевает Per Graph.
    Если мы создаем некоторого рода универсальный код, который принимает в качестве входной информации экземпляр
    ILifecycle и передает его в метод
    LifecycleIs
    , то можем использовать его для конфигурирования компонента с помощью стиля существования Per Graph. Наличие null подразумевает Per Graph, поэтому два приведенных ниже примера, эквивалентны: container.Configure(r => r
    .For()
    .LifecycleIs(null)
    .Use()); и container.Configure(r => r
    .For()
    .Use());
    П одсказка
    Несмотря на то, что вы можете использоват ь null для того, чтобы дать намек на Per
    Graph, лучше всего полностью опустить объявление стиля существования.
    В то время как API, раскрывающееся при помощи метода
    Configure и
    ConfigurationExpression
    , позволяет нам явным образом объявлять стиль существования, Scan API, основанное на соглашениях, не позволяет нам сделать это. В интерфейсе
    IAssemblyScanner нет ни одного метода, который дал бы нам возможность одним махом явно объявить стиль существования для набора компонентов.
    Т ем не менее, мы можем реализовать простой
    IRegistrationConvention
    , который может объявить стиль существования для набора компонентов одним махом. Ниже приведен пример использования экземпляра
    IRegistrationConvention под названием
    SingletonConvention
    : container.Configure(r => r.Scan(s =>
    { s.AssemblyContainingType(); s.AddAllTypesOf(); s.Convention();
    }));
    Обратите внимание на то, что здесь приведена такая же конфигурация, как и в первом примере автоматической регистрации из раздела 11.1.2 "Конфигурирование контейнера".
    Вы добавили только одну строку кода, которая добавляет
    SingletonConvention
    , продемонстрированный в приведенном ниже листинге.

    428
    Листинг 11-3: Реализация соглашения по объявлению стиля существования public class SingletonConvention : IRegistrationConvention
    { public void Process(Type type, Registry registry)
    { registry.For(type).Singleton();
    }
    }
    Если вы помните предыдущее обсуждение
    IRegistrationConvention из листинга 11-1, то помните и то, что метод
    Process вызывается в сборке операции
    Scan для каждого включенного типа. В этом случае единственное, что вам необходимо сделать – объявить стиль существования для каждого типа, используя метод
    Singleton
    . Таким образом, мы сконфигурируем каждый тип в виде Singleton.
    Используя код в качестве конфигурации, мы можем конфигурировать компоненты, имеющие различные стили существования, тем способом, которым только захотим.
    Несмотря на то, что это намного более гибкий способ конфигурирования компонентов, нам иногда нужно обращаться к XML с целью получения позднего связывания. В этом случае мы также можем объявлять стили существования.
    Конфигурирова ние стилей суще ствования с помощью XML
    В ситуациях, когда нам нужно определять компоненты в XML, мы еще хотим в этом же самом месте уметь конфигурировать их стили существования. Это легко выполняется в виде составляющей части XML-схемы, введенной в разделе 11.1.2 "Конфигурирование контейнера". Для объявления стиля существования можно использовать необязательный атрибут
    Scope
    :
    PluggedType="Ploeh.Samples.MenuModel.Steak,
    ➥Ploeh.Samples.MenuModel"
    Scope="Singleton" />
    Единственное отличие этого примера от примера из раздела 11.1.2 – добавленный атрибут, который конфигурирует экземпляр в виде Singleton. Когда вы ранее опускали атрибут
    Scope
    , автоматически применялся Per Graph, используемый в StructureMap по умолчанию.
    И в коде, и в XML конфигурировать стили существования для компонентов легко. Во всех случаях это выполняется в довольно декларативной манере. Несмотря на то, что выполнять конфигурацию легко, вы должны не забывать, что в некоторые стили существования включаются объекты-долгожители, которые, пока существуют, используют память.
    П редотвращение утечек памяти
    Как и любой другой DI-контейнер StructureMap создает для нас диаграмму объектов, но не отслеживает созданные объекты. Он может отслеживать эти объекты в своих собственных целях, но зависит это от жизненного цикла объекта. К примеру, для того чтобы реализовать область применения Singleton, StructureMap должен сохранять ссылку на созданный экземпляр. Это справедливо и для стиля существования HttpContext, в котором

    429 все экземпляр ы хранятся в
    HttpContext.Current.Items
    . Тем не менее, после завершения
    HTTP-запроса все эти экземпляры выходят за пределы области применения и могут быть уничтожены сборщиком мусора.
    С другой стороны, стили существования Per Graph и T ransient не отслеживают созданные
    StructureMap объекты. Как вы видели в листингах 8-7 и 8-8, экземпляры объектов создаются и возвращаются без внутреннего сопровождения. Это имеет некоторые преимущества и недостатки.
    Поскольку StructureMap особо не держится за экземпляры, риск появления неумышленных утечек памяти в этом случае намного меньше. Для такого контейнера, как
    Castle W indsor, утечки памяти будут гарантированно возникать, если мы забудем вызвать метод
    Release для всех разрешенных диаграмм объектов. Такого не происходит со
    StructureMap, поскольку, как только объекты выйдут за рамки области применения, они будут уничтожены сборщиком мусора.
    Недостаток заключается в том, что устраняемые объекты не могут детерминированно уничтожаться. Т ак как мы не можем явным образом высвобождать диаграмму объектов, мы не можем уничтожать какие-либо устраняемые объекты. Это означает, что обертывание устраняемых API в неустраняемые сервисы, которое обсуждалось в разделе
    6.2.1, становится еще более значимым.
    Короче говоря, StructureMap позволяет сборщику мусора уничтожать объекты после того, как в вашем коде они выходят за рамки области применения, но наши собственные классы должны вести себя точно также. Мы не можем полагаться на то, что контейнер или код, осуществляющий вызов, будут уничтожать любые сервисы, поэтому мы должны продолжать использовать устраняемые объекты в рамках единичных методов.
    Встроенные в StructureMap стили существования представляют собой довольно исчерпывающий набор, который должен удовлетворять самым повседневным нуждам.
    Помимо этого в редких случаях, когда нам нужен специализированный стиль существования, у нас есть возможность создать свой собственный стиль существования.
    Разработка пользовательского стиля существования
    В большинстве случаев мы должны уметь выходить из разных ситуаций с помощью исчерпывающего набора стилей существования, уже предоставляемых StructureMap, но если у нас имеются особые нужды, можно реализовать пользовательский стиль существования. В данном разделе вы увидите, как это сделать. После краткого обзора
    Seam
    , который делает возможным создание пользовательского стиля существования, большую часть времени мы потратим на пример.
    П онимание API стиля существования
    В разделе 11.1.2 "Конфигурирование контейнера" вы уже получили некоторое представление об API стилей существования StructureMap. Метод
    LifecycleIs принимает в качестве параметра экземпляр интерфейса
    ILifecycle
    , который моделирует то, как стили существования взаимодейству ют с остальной частью контейнера StructureMap:

    430 public interface ILifecycle
    { string Scope { get; } void EjectAll();
    IObjectCache FindCache();
    }
    Среди этих трех методов центральным методом является
    FindCache
    . Он возвращает кэш, который StructureMap использует для поиска и вставки объектов, имеющих конкретный стиль существования. Интерфейс
    ILifecycle
    , главным образом, выступает в роли абстрактной фабрики для экземпляров
    IObjectCache
    , в которых содержится реализация стиля существования. Этот интерфейс довольно-таки сложен, но его не столь сложно реализовать: public interface IObjectCache
    { object Locker { get; } int Count { get; } bool Has(Type pluginType, Instance instance); void Eject(Type pluginType, Instance instance); object Get(Type pluginType, Instance instance); void Set(Type pluginType, Instance instance, object value); void DisposeAndClear();
    }
    Большинство методов данного интерфейса имеют дело с поиском, передачей или удалением экземпляра на основании
    Type и
    Instance
    . Рисунок 11-5 иллюстрирует, каким образом StructureMap взаимодейст вует с реализацией
    IObjectCache
    Рисунок 11-5: StructureMap взаимодействует с интерфейсом
    IObjectCache
    , в первую очередь, вызывая метод
    Get для объекта-кэша. Если кэш возвращает значение, то данное значение используется незамедлит ельно. В противном случае StructureMap создает новое значение и добавляет это значение в кэш перед тем, как его вернуть.

    431
    П римечание
    Механизм, проиллюстрированный на рисунке 11-5, похож на взаимодействие между
    Unity и
    ILifetimePolicy
    , продемонстрированное на рисунке 14-6.
    Сначала StructureMap пытается получить запрашиваемый экземпляр из метода
    Get
    . Если этот метод возвращает значение null для предоставленных
    Type и
    Instance
    , то
    StructureMap создает запрашиваемый экземпляр и перед тем, как его вернуть, добавляет этот экземпляр в кэш посредством метода
    Set
    Давайте на примере рассмотрим, как это работает.
    Разработка стиля существования Caching
    В данном примере вы будете разрабатывать такой же стиль существования Caching, как вы создавали для Castle W indsor в разделе 10.2.3 "Разработка пользовательского стиля существования". Короче говоря, этот стиль существования кэширует и повторно использует экземпляры в течение некоторого времени перед тем, как их высвободить.
    П редупреждение
    Этот шаблонный код не является потоко-безопасным, но соответствующая рабочая реализация должна быть потоко-безопасной, поскольку, скорее всего, несколько потоков будут одновременно пытаться разрешать объекты из контейнера.
    Давайте начнем с самой простой составляющей. Следующий листинг демонстрирует реализацию интерфейса
    ILifecycle
    Листинг 11-4: Реализация
    ILifecycle
    1.
    public partial class CacheLifecycle : ILifecycle
    2.
    {
    3.
    private readonly LeasedObjectCache cache;
    4.
    public CacheLifecycle(ILease lease)
    5.
    {
    6.
    if (lease == null)
    7.
    {
    8.
    throw new ArgumentNullException("lease");
    9.
    }
    10.
    this.cache = new LeasedObjectCache(lease);
    11.
    }
    12.
    public void EjectAll()
    13.
    {
    14.
    this.FindCache().DisposeAndClear();
    15.
    }
    16.
    public IObjectCache FindCache()
    17.
    {
    18.
    return this.cache;
    19.
    }
    20.
    public string Scope
    21.
    {
    22.
    get { return "Cache"; }
    23.
    }
    24.
    }
    Строка 10: Сохраняет lease в пользовательском cache

    432
    Строка 12, 16, 20: Члены
    ILifecycle
    Строка 18: Возвращает пользовательский cache
    Класс
    CacheLifecycle
    , как и требуется, реализует интерфейс
    ILifecycle
    . Для получения экземпляра
    ILease он использует паттерн Constructor Injection. Интерфейс
    ILease
    – локальный вспомогательный (
    1   ...   26   27   28   29   30   31   32   33   ...   43


    написать администратору сайта