Pull to refresh

Microsoft Object Builder

Reading time 9 min
Views 2K
Всем привет! Это моя первая статья в Хабре. Столкнувшись сегодня в ленте на описание Java-фреймворков Spring и Tapestry, решил просмотреть Хабру и найти ценителей «конкурирующих» фреймворков от Microsoft – в частности Composite UI Application Block (CAB) и Unity. К моему удивлению, ничего не нашел. Увидев в коментах в статье про Java-фреймворки просьбу описать механизм инъекций зависимостей, решил начать свой цикл статей про .Net-фреймворки именно с разъяснения вопросов IoC. Итак, встречайте – краеугольный камень (в прошлом) замечательного движка CAB – Microsoft Object Builder.

Что это такое и где используется


Object Builder (далее OB) – это механизм неявной автоматической композиции множества зависимых объектов в связанный граф. OB был предоставлен компанией Microsoft в мае 2005 года через свою кузницу «сложных проектов» с открытыми кодами Patterns and Practices в тандеме с каркасом десктоп-приложений – CAB (или SmartClient). Набор основополагающих классов CAB использует OB как механизм инъекции зависимостей при создании пользователем объектов движка (пользовательских контролов, сервисов, мест расширения, и т.п.).

Как поставить себе


Скачать CAB отсюда и поставить. Инсталлятор добавит в GAC три сборки, одна из них OB.

Из чего состоит Object Builder и как он работает


По сути, OB это агрегация нескольких паттернов программирования. Вкратце опишу структуру.

  • Builder – представляет из себя фасад, точку входа в механизм построителя объектов. Предоставляет пользователю методы построения (BuildUp), метода разрушения (TearDown). Builder содержит в себе цепочку стратегий (Strategy), каждая из которых является атомарной частью построителя. К тому же, Builder обладает возможностью вызвать процесс построения объекта нужным пользователю методом – через потребление т.н. политик (Policy) построения. Когда пользователь хочет построить объект, он вызывает BuildUp и передает в метод тип создаваемого объекта. На выходе получает инстанс этого типа.
  • Strategy – частичка, из которой состоит OB. Стратегия имплементирует какой-то способ построения объекта. Во время своей работы, Builder идет по цепочке и применяет каждую стратегию из нее к создаваемому объекту – тем самым реализуя связи между зависимыми объектами. Пользователь может создать свою стратегию и кастомизировать процесс создания объектов.
  • Policy – политика построения объектов. Каждая стратегия может обладать какой-то политикой. Политика предоставляет стратегии контекст (т.е. объектное окружение) в момент создания объектов. Политика может служить для связи стратегий друг с другом. Достаточно сложная штука, но, с высоты прожитых лет, могу сказать, что Microsoft сделало функционал OB достаточно богатым и (обычно) создание новых политик построения не требуется – достаточно встроенных.
  • Locator и LifeTimeContainer – для того, чтобы знать, откуда брать уже построенные объекты зависимостей, OB требуется контейнер уже созданных объектов. Locator – это специальный класс, который может найти в LifeTimeContainer объект и представить его Builder для вставки зависимости через WeakReference. Соответственно, LifeTimeContainer управляет жизненным циклом объектов (т.е. он знает, когда объекты надо удалить из памяти, а когда не надо).
  • Stage – в OB есть жизнный цикл построения объекта. Это несколько этапов:
    1. Перед созданием (PreCreation)
    2. Создание (Creation)
    3. Инициализация (Initialization)
    4. Пост-инициализация (PostInitialization)
    Каждая стратегия OB работает на каком-то этапе.


Душа требует примеров! Для примера, создадим объект – микросервис IProgramService:
interface IProgramService
{
  void DoWork();
}


* This source code was highlighted with Source Code Highlighter.

А вот его реализация:
class ProgramService : IProgramService
{
  #region IProgramService Members

  public void DoWork()
  {
    Console.WriteLine(string.Format("[{0}] Program Service: DoWork()", _guid));
  }

  #endregion

  private Guid _guid = Guid.NewGuid();
  public Guid Guid
  {
    get { return _guid; }
  }
}


* This source code was highlighted with Source Code Highlighter.

Тогда код создания будет такой:
Builder builder = new Builder();
Locator locator = new Locator();
IProgramService service = builder.BuildUp<ProgramService>(locator, null, null);

service.DoWork();


* This source code was highlighted with Source Code Highlighter.

Вот так просто.

Перейдем к рассмотрению стратегий вставки зависимостей.

Применение


Если посмотреть на код создания билдера, можно увидеть, как он добавляет в себя жизненно-необходимые стратегии:

public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
  Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
  Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
  Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
  Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
  Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
  Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
  Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
  Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
  Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);

  Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());

  if (configurator != null)
    configurator.ApplyConfiguration(this);
}


* This source code was highlighted with Source Code Highlighter.


Разберемся, какими методами можно создавать связи между зависимостями и как работают эти стратегии.

TypeMappingStrategy (PreCreation) – Политика соответствия типов. Например, в прошлом примере мы явно указали, какой тип нам создавать. Но что, если мы не хотим каждый раз указывать, какой класс реализует интерфейс IProgramService? Что, если мы хотим создать IProgramService, а кто будет конкретным типом, знать не хотим – пусть об этом голова болит у программиста Васи? Воспользуемся политикой создания ITypeMappingPolicy. Эта политика укажет локатору соответствие между интерфейсом и конкретным типом:

class TypeMappingPolicy : ITypeMappingPolicy
{
  #region ITypeMappingPolicy Members

  public DependencyResolutionLocatorKey Map(DependencyResolutionLocatorKey incomingTypeIDPair)
  {
    return new DependencyResolutionLocatorKey(typeof(ProgramService), null);
  }

  #endregion
}


* This source code was highlighted with Source Code Highlighter.


Регистрация политики происходит таким способом:
builder.Policies.Set<ITypeMappingPolicy>(new TypeMappingPolicy(), typeof(IProgramService), null);
IProgramService service = builder.BuildUp<IProgramService>(locator, null, null);


* This source code was highlighted with Source Code Highlighter.


SingletonStrategy (PreCreation) – Стратегия создания объекта как синглтона. Это значит, что создаваемый объект для указанного типа создастся всего один раз. Все последующие попытки создания такого типа будут замещаться подстановкой уже существующего объекта. Для этого нужно явно пометить создаваемый тип специальным ключем, т.н. DependencyResolutionLocatorKey:
IProgramService service = builder.BuildUp<ProgramService>(locator, null, null);
DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(typeof(ProgramService), null);
locator.Add(key, service);

IProgramService service2 = builder.BuildUp<ProgramService>(locator, null, null);


* This source code was highlighted with Source Code Highlighter.

В этом случае service и service2 указывают на один и тот же объект.

ConstructorReflectionStrategy (PreCreation) – стратегия создания объекта через конструктор. Название говорит само за себя – стратегия выберет конструктор объекта, помеченный атрибутом [InjectionConstructor] и для каждого помеченного атрибутом [Dependency] параметра найдет его через локатор. После чего вызовет конструктор, передав туда отрезолвленные параметры.
[InjectionConstructor]
public ServiceConsumer([Dependency]IProgramService service)
{
  _service = service;
}


* This source code was highlighted with Source Code Highlighter.

Существует несколько способов вставки параметров. Через свойства атрибута Dependency можно указать создаваемый тип, так же определить поведение, если разыскиваемый тип не найден. Например, создать новый (CreateNew), или бросить исключение:
[InjectionConstructor]
public ServiceConsumer(
  [Dependency(CreateType=typeof(ProgramService), NotPresentBehavior=NotPresentBehavior.Throw)]IProgramService service)
{
  _service = service;
}


* This source code was highlighted with Source Code Highlighter.

Само собой разумеется, если мы создаем один объект, у которого есть зависимость на другой, то другой объект должен резолвится в локаторе:
IProgramService service = builder.BuildUp<ProgramService>(locator, null, null);
DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(typeof(IProgramService), null);
locator.Add(key, service);

ServiceConsumer consumer = builder.BuildUp<ServiceConsumer>(locator, null, null);


* This source code was highlighted with Source Code Highlighter.


CreationStrategy (Сreation) – это обычная стратегия создания. В самом первом примере выше объект строится как раз через стратегию создания. Как она работает – сначала стратегия ищет пользовательскую политику построения ICreationPolicy, и, если не находит политику, пользуется «родной». Родная политика создания инструктирует билдер определить первый конструктор объекта (через рефлексию) и запустить цепочку построения на каждую зависимость (параметр) в конструкторе. Затем исполнить его через Activator. Особенностью этой стратегии служит то, что в нее можно передать уже созданный объект. Тогда стратегия делать ничего не будет :) Просто передаст управление другим стратегиям, у которых Stage следующий за Creation. Еще раз последнее, но другими словми — мы можем создать объект через new(), передать его билдеру. В этом случае билдер не будет создавать объект, а просто вставит в него зависимости.

PropertyReflectionStrategy + PropertySetterStrategy (Initialization). Стратегии (на самом деле, семантически, это одна стратегия), позволяющие вставлять зависимости в свойства объекта. Вкратце, работа такая – PropertyReflectionStrategy определяет свойства объекта, помеченные атрибутом [Dependency], узнает типы объектов, которые туда надо вставить, создает политику IPropertySetterPolicy, где перечисляет эту информацию. Далее, идя по цепочке, начинает работать PropertySetterStrategy, которая находит политику, из нее определяет свойства, нуждающиеся в вставке объектов, резолвит объекты и устанавливает их в нужные места. Фишка разделения стратегий здесь для того, чтобы пользователь смог использовать свои IPropertySetterPolicy.
[Dependency(CreateType=typeof(ProgramService),
  NotPresentBehavior=NotPresentBehavior.CreateNew)]
public IProgramService ProgramService
{
  set { _service = value; }
  get { return _service; }
}


* This source code was highlighted with Source Code Highlighter.


Этот код можно переписать проще:
[CreateNew]
public ProgramService ProgramService2
{
  set { _service = value; }
}


* This source code was highlighted with Source Code Highlighter.


MethodReflectionStrategy + MethodExecutionStrategy (Initialization). По сути дела, эти две стратегии изоморфны стратегиям Property-зависимостей. Только, вместо установки свойства, происходит вызов метода, помеченного атрибутом [InjectionMethod].
[InjectionMethod]
public void DoWork([Dependency]IProgramService service)
{
  service.DoWork();
}


* This source code was highlighted with Source Code Highlighter.


BuilderAwareStrategy (PostInitialization). В OB есть такой специальный интерфейс IBuilderAware, в котором есть два метода – OnBuiltUp, OnTearingDown. Можно реализовать их в своем классе, тогда при построении (или уничтожении) объекта, стратегия их вызовет. Очень полезно, если нужно запустить что-то сразу же после создания объекта (когда все зависимости будут на своем месте).
#region IBuilderAware Members

public void OnBuiltUp(string id)
{
  Console.WriteLine("Built Up!");
}

public void OnTearingDown()
{
  Console.WriteLine("Tearing down!");
}

#endregion


* This source code was highlighted with Source Code Highlighter.


Я перечислил все «родные» стратегии OB. В движках разработчики добавляют свои стратегии, чтобы можно было связвать специализированные аспекты фреймворка. К сожалению, полностью разобрать все аспекты OB — так, скажем, не затронуты именованые инстансы, не развернута полностью тема политик и т.п. Читатель, думаю, если захочет, разберется сам:)

Как это использовать в реальных приложениях, плюсы и минусы


Ответ прост – как инфраструктурный аспект. Если вы хотите внедрить у себя в приложении архитектуру, подразумевающую инъекцию зависимостей, то OB – то, что доктор прописал. Сам OB изначально не подразумевался для прямого использования. В движке CAB его методы оборачиваются специальными коллекциями – фасадами OB, что очень облегчает работу программиста.

Минусы: Самым, пожалуй, большой минус решения – OB тяжелый. Он загромождает код атрибутами (без них не обойтись!), а его метод BuildUp, сами понимаете, не из самых быстрых. Решить эту проблему можно оптимизацией связей в графе классов – собственно, минимизировать количество зависимостей при максимальной изолированности кода.
Плюсы: Низкая связность кода, объектов и все отсюда вытекающие. Это и простота юнит-тестов, и автономная (когда ручками ничего не надо выставлять) микросервисная архитектура и тому подобное.

Будущее Object Builder и заключение.


Сейчас OB уже устарел :( Да, простите, я писал статью по устаревшей технологии, но, мне кажется, полезно начать с основ. К тому же, будет сложно говорить о Microsoft Object Builder 2, не понимая, чем же был сам Microsoft Object Builder. Microsoft замещает движок CAB на более современный – Unity. Unity Application Block – это еще более гибкая платформа с долгожданной поддержкой WPF. Собственно, в следующих статьях я расскажу, что такое CAB и постепенно перейду на Unity. Кому не терпится узнать, что это такое, вот ссылки:
CAB: msdn.microsoft.com/en-us/library/aa480450.aspx
CAB: www.codeplex.com/smartclient
Unity: msdn.microsoft.com/en-us/library/cc468366.aspx
Unity: www.codeplex.com/unity
Отличие CAB от Unity: www.codeplex.com/CompositeWPF/Release/ProjectReleases.aspx?ReleaseId=16941

Спасибо за внимание.
Tags:
Hubs:
+13
Comments 15
Comments Comments 15

Articles