Пользователь
0,0
рейтинг
20 апреля 2013 в 15:28

Разработка → Руководство разработчика Prism — часть 3, управление зависимостями между компонентами перевод tutorial

C#*, .NET*
Оглавление
  1. Введение
  2. Инициализация приложений Prism
  3. Управление зависимостями между компонентами
  4. Разработка модульных приложений
  5. Реализация паттерна MVVM
  6. Продвинутые сценарии MVVM
  7. Создание пользовательского интерфейса
    1. Рекомендации по разработке пользовательского интерфейса
  8. Навигация
    1. Навигация на основе представлений (View-Based Navigation)
  9. Взаимодействие между слабо связанными компонентами

Приложения, созданные с помощью библиотеки Prism, обычно являются составными приложениями, потенциально состоящими из слабо связанных служб и компонентов. Они должны взаимодействовать друг с другом так, чтобы предоставлять содержание пользовательскому интерфейсу и получать уведомления о действиях пользователя. Поскольку они слабо связаны, им необходим способ взаимодействия, без которого необходимая функциональность не сможет быть получена.

Для связи всех частей воедино, Prism приложения полагаются на DI контейнер. DI контейнеры уменьшают зависимости между объектами, предоставляя способ создания экземпляров классов и управления их временем жизни в зависимости от конфигурации контейнера. При создании объектов с помощью контейнера, он инжектирует в них необходимые зависимости. Если зависимости ещё не были созданы, то контейнер в начале создаёт их и разрешает их собственные зависимости. В некоторых случаях, сам контейнер внедряется как зависимость. Например, при использовании Unity, в модули внедряется контейнер для того, чтобы они могли зарегистрировать в нём свои представления и службы.

Есть несколько преимуществ использования контейнера:
  • Контейнер устраняет потребность компонента определять местоположение его зависимостей или управлять временем их жизни.
  • Контейнер позволяет заменять реализации, не влияя на компоненты.
  • Контейнер облегчает тестируемость, позволяя внедрять в объекты фальшивые зависимости.
  • Контейнер упрощает обслуживание, позволяя легко добавлять к системе новые компоненты.

В контексте приложения, основанного на библиотеке Prism, есть определённые преимущества использования контейнера:
  • Контейнер разрешает зависимости модуля при его загрузке.
  • Контейнер используется для регистрации и создания моделей представления и представлений.
  • Контейнер может создавать модели представления и внедрять представления.
  • Контейнер внедряет службы составного приложения, такие как менеджер регионов, или агрегатор событий.
  • Контейнер используется для регистрации специфичных для модуля служб со специфичной для модуля функциональностью.


Заметка
Некоторые примеры в руководстве Prism используют контейнер Unity Application Block (Unity). Другие, например Modularity QuickStarts, используют Managed Extensibility Framework (MEF). Библиотека самого Prism не зависима от применяемого контейнера, и вы можете использовать её службы и паттерны с другими контейнерами, такими как CastleWindsor, Autofac, Structuremap, Spring.NET, или с любым другим.

Ключевое решение: выбор контейнера внедрения зависимостей


Библиотека Prism предоставляет два DI контейнера по умолчанию: Unity и MEF. Prism расширяема, таким образом вы можете использовать другие контейнеры, написав небольшое количество кода для их адаптации. И Unity, и MEF обеспечивают одинаковую основную функциональность, необходимую для внедрения зависимостей, даже учитывая то, что они работают сильно по-разному. Некоторые из возможностей, предоставляемые обоими контейнерами:
  • Оба позволяют регистрировать типы в контейнере.
  • Оба позволяют регистрировать экземпляры в контейнере.
  • Оба позволяют принудительно создавать экземпляры зарегистрированных типов.
  • Оба внедряют экземпляры зарегистрированных типов в конструкторы.
  • Оба внедряют экземпляры зарегистрированных типов в свойства.
  • У них обоих есть декларативные атрибуты для управления типами и зависимостями.
  • Они оба разрешают зависимости в графе объектов.

Unity предоставляет несколько возможностей, которых нет в MEF:
  • Разрешает конкретные типы без регистрации.
  • Разрешает открытые обобщения (Generics).
  • Может использовать перехват вызова методов для добавления дополнительной функциональности к целевому объекту (Interception).

MEF предоставляет несколько возможностей, которых нет в Unity:
  • Самостоятельно обнаруживает сборки в каталоге файловой системы.
  • Загружает XAP файлы и ищет в них сборки.
  • Проводит рекомпозицию свойств и коллекций при обнаружении новых типов.
  • Автоматически экспортирует производные типы.
  • Поставляется вместе с .NET Framework, начиная с четвёртой версии.

Контейнеры различаются по возможностям и работают по-разному, но библиотека Prism может работать с любым контейнером, обеспечивая одинаковую функциональность. Рассматривая, какой контейнер использовать, имейте в виду свой предыдущий опыт и определите, какой контейнер лучше подходит для сценариев вашего приложения.

Соображения по использованию контейнера


Что следует рассмотреть перед использованием контейнеров:
  • Рассмотрите, уместно ли регистрировать и разрешать компоненты, используя контейнер:
    • Рассмотрите, является ли воздействие на производительность при регистрации в контейнере и разрешении экземпляров, приемлемым для вашего случая. Например, если вы должны создать 10000 многоугольников, чтобы нарисовать что-то внутри метода отрисовки, то создание всех многоугольников через контейнер может привести к существенной потере производительности.
      Заметка.
      Некоторые контейнеры способны разрешать экземпляры объектов почти так же быстро, как и их создание через ключевое слово new. Но, в любом случае, разрешение через контейнер большого количества объектов в цикле, должно быть серьёзно обосновано.
    • Если присутствует множество глубоких зависимостей, то затраты по времени на их разрешение могут существенно возрасти.
    • Если компонент не имеет зависимостей или сам не является зависимостью для других типов, возможно, не имеет смысла пользоваться контейнером при его создании, или помещать его в контейнер, соответственно.
    • Если у компонента есть единственный набор зависимостей, которые являются неотъемлемой его частью и никогда не будут изменяться, возможно, не имеет смысла пользоваться контейнером при его создании. Хотя, в этом случае, его тестирование может усложниться.

  • Рассмотрите, должен ли компонент быть зарегистрирован как синглтон, или как экземпляр:
    • Если компонент является глобальной службой, которая действует как менеджер единственного ресурса, например служба протоколирования, то можно зарегистрировать его как синглтон.
    • Если компонент даёт доступ к общему состоянию многочисленным потребителям, то его можно зарегистрировать как синглтон.
    • Если объект нуждается в создании нового экземпляра каждый раз при внедрении, то его нельзя регистрировать как синглтон. Например, каждое представление, вероятно, нуждается в новом экземпляре модели представления.

  • Рассмотрите, хотите ли вы конфигурировать контейнер в коде или через файл конфигурации:
    • Если вы хотите централизованно управлять всеми службами, используйте файл конфигурации.
    • Если вы хотите регистрировать различные службы в зависимости от каких-либо обстоятельств, конфигурируйте контейнер в коде.
    • Если у вас есть службы уровня модуля, конфигурируйте контейнер через код так, чтобы они были зарегистрированы только при загрузке модуля.


Заметка
Некоторые контейнеры, такие как MEF, не могут быть сконфигурированы через конфигурационный файл и должны быть сконфигурированы в коде.

Базовые сценарии


Контейнеры используются для двух основных целей, а именно: регистрация и разрешение.

Регистрация


Прежде, чем можно будет внедрить зависимости в объект, типы зависимостей должны быть зарегистрированы в контейнере. Регистрация типа обычно включает передачу контейнеру интерфейса и конкретного типа, который реализует этот интерфейс. Есть, прежде всего, два способа регистрации типов и объектов: в коде, или через файл конфигурации. Детали реализации могут изменяться в зависимости от контейнера.

Как правило, есть два способа зарегистрировать типы и объекты в контейнере в коде:
  • Можно зарегистрировать тип, или отображение одного типа в другой. В подходящее время контейнер создаст экземпляр типа, который вы задали.
  • Можно зарегистрировать существующий экземпляр объекта, как синглтон. Контейнер возвратит ссылку на существующий объект.


Регистрация типов в UnityContainer

Во время инициализации, тип может зарегистрировать другие типы, такие как представления и службы. Регистрация позволяет разрешать их зависимости контейнером и стать доступными другим типам. Чтобы сделать это, необходимо внедрить контейнер в конструктор модуля. Следующий код показывает, как OrderModule из Commanding QuickStart регистрирует тип репозитория при инициализации, как синглтон.

public class OrderModule : IModule {
    public void Initialize() {
        this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
        ...
    }
    ...
}

В зависимости от того, какой контейнер вы используете, регистрация может также быть выполнена вне кода через файл конфигурации. Для примера смотрите, «Registering Modules using a Configuration File» в Главе 4, "Modular Application Development."

Регистрация типов с контейнером MEF

Для регистрации типов в контейнере, MEF использует систему, основанную на атрибутах. В результате довольно легко добавить регистрацию типа к контейнеру: для этого требуется добавить атрибут [Export] к типу, который вы хотите зарегистрировать в контейнере, как показано в следующем примере.

[Export(typeof(ILoggerFacade))]
public class CallbackLogger: ILoggerFacade {
   ...
}

Другим вариантом использования MEF, может быть создание экземпляра класса и регистрация именно этого экземпляра в контейнере. QuickStartBootstrapper в Modularity for Silverlight with MEF QuickStart показывает пример этого в методе ConfigureContainer.

protected override void ConfigureContainer() {
    base.ConfigureContainer();
    // Поскольку мы создали CallbackLogger, и он должен использоваться сразу,
    // мы проводим его композицию, чтобы удовлетворить любой импорт (зависимость), который он имеет.
    this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger);
}

Заметка
При использовании MEF как контейнера рекомендуется использование именно атрибутов для регистрации типов.

Разрешение


После того, как тип зарегистрирован, он может быть разрешён или внедрён как зависимость. Когда тип разрешается, и контейнер должен создать новый экземпляр этого типа, то он внедряет зависимости в этот экземпляр.

Вообще, когда тип разрешается, происходит одна из трёх вещей:
  • Если тип не был зарегистрирован, контейнер выдаёт исключение.
    Заметка
    Некоторые контейнеры, включая Unity, позволяют разрешать конкретный тип, который не был зарегистрирован.
  • Если тип был зарегистрирован как синглтон, контейнер возвращает экземпляр синглтона. Если это первый вызов, контейнер может создать экземпляр и сохранить его для будущих вызовов.
  • Если тип не был зарегистрирован как синглтон, контейнер возвращает новый экземпляр.
    Заметка
    По умолчанию, типы, зарегистрированные в MEF, являются синглтонами, и контейнер хранит ссылки на объекты. В Unity, по умолчанию, возвращаются новые экземпляры объектов, и контейнер не сохраняет на них ссылок.

Разрешение экземпляров в Unity

Следующий пример кода из Commanding QuickStart показывает, как представления OrdersEditorView и OrdersToolBar разрешаются из контейнера для привязки их к соответствующим регионам.

public class OrderModule : IModule {
    public void Initialize() {
        this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());

        // Показываем представление Orders Editor в главном регионе оболочки.
        this.regionManager.RegisterViewWithRegion("MainRegion",
                                                    () =>; this.container.Resolve<OrdersEditorView>());

        // Показываем представление Orders Toolbar в регионе панели инструментов.
        this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion",
                                                    () => this.container.Resolve<OrdersToolBar>());
    }
    ...
}

Конструктор OrdersEditorPresentationModel содержит следующие зависимости (репозиторий заказов и прокси команды заказов), которые вводятся при его разрешении.

public OrdersEditorPresentationModel(IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy) {
    this.ordersRepository = ordersRepository;
    this.commandProxy     = commandProxy;

    // Создание фиктивных данных о заказе.
    this.PopulateOrders();

    // Инициализация CollectionView для основной коллекции заказов.
#if SILVERLIGHT
    this.Orders = new PagedCollectionView( _orders );
#else
    this.Orders = new ListCollectionView( _orders );
#endif

    // Отслеживание текущего выбора.
    this.Orders.CurrentChanged += SelectedOrderChanged;
    this.Orders.MoveCurrentTo(null);
}

В дополнение к внедрению в конструктор, как показано в предыдущем примере, Unity также может внедрять зависимости в свойства. Любые свойства, к которым применён атрибут [Dependency], автоматически разрешаются и внедряются, при разрешении объекта. Если свойство помечено атрибутом OptionalDependency, то при невозможности разрешить зависимость, свойству присваивается null и исключение не генерируется.

Разрешение экземпляров в MEF

Следующий пример кода показывает, как Bootstrapper в Modularity for Silverlight with MEF QuickStart получает экземпляр оболочки. Вместо того чтобы запросить конкретный тип, код мог бы запросить экземпляр интерфейса.

protected override DependencyObject CreateShell() {
    return this.Container.GetExportedValue<Shell>();
}

В любом классе, который разрешается MEF, можно также использовать инжекцию в конструктор, как показано в следующем примере кода из ModuleA в Modularity for Silverlight with MEF QuickStart, у которого внедряются ILoggerFacade и IModuleTracker.

[ImportingConstructor]
public ModuleA(ILoggerFacade logger, IModuleTracker moduleTracker) {
    if (logger == null) {
        throw new ArgumentNullException("logger");
    } 
    if (moduleTracker == null) {
        throw new ArgumentNullException("moduleTracker");
    }
	
    this.logger = logger;
    this.moduleTracker = moduleTracker;
    this.moduleTracker.RecordModuleConstructed(WellKnownModuleNames.ModuleA);
}

С другой стороны, можно использовать инжекцию свойства, как показано в классе ModuleTracker из Modularity for Silverlight with MEF QuickStart, у которого есть экземпляр внедряемого ILoggerFacade.

[Export(typeof(IModuleTracker))]
public class ModuleTracker : IModuleTracker {
     // Из-за ограничений Silverlight/MEF, поле должно быть общедоступно.
     [Import] public ILoggerFacade Logger;
}

Заметка
В Silverlight импортируемые свойства и поля должны быть общедоступными.

Использование контейнеров внедрения зависимостей и служб в Prism


Контейнеры внедрения зависимости, используются, чтобы удовлетворить зависимости между компонентами. Удовлетворение этих зависимостей обычно включает регистрацию и разрешение. Библиотека Prism предоставляет поддержку для контейнеров Unity и MEF, но не зависит от них. Поскольку библиотека имеет доступ к контейнеру через интерфейс IServiceLocator, контейнер может быть легко заменён. Чтобы сделать это, вы должны реализовать интерфейс IServiceLocator. Обычно, если вы замените контейнер, то вы должны будете также написать свой собственный контейнерно-специфичный загрузчик. Интерфейс IServiceLocator определяется в Common Service Locator Library. Это open source проект по обеспечению абстракции контейнеров IoC (Inversion of Control), таких как контейнеры внедрения зависимостей, и локаторы службы. Цель использования этой библиотеки состоит в том, чтобы использовать IoC и Service Location, без предоставления определённой реализации контейнера.

Библиотека Prism предоставляет UnityServiceLocatorAdapter и MefServiceLocatorAdapter. Оба адаптера реализуют интерфейс ISeviceLocator, расширяя тип ServiceLocatorImplBase. Следующая иллюстрация показывает иерархию классов.

Реализации Common Service Locator в Prism.

Хотя библиотека Prism не ссылается и не полагается на определённый контейнер, для приложения характерно использовать вполне конкретный DI контейнер. Это означает, что для приложения разумно ссылаться на определённый контейнер, но библиотека Prism не ссылается на контейнер непосредственно. Например, приложение Stock Trader RI и несколько из QuickStarts, используют Unity в качестве контейнера. Другие примеры и QuickStarts используют MEF.

IServiceLocator


Следующий код показывает интерфейс IServiceLocator и его методы.

public interface IServiceLocator : IServiceProvider {
    object GetInstance(Type serviceType);
    object GetInstance(Type serviceType, string key);
    IEnumerable<object> GetAllInstances(Type serviceType);
    TService GetInstance<TService>();
    TService GetInstance<TService>(string key);
    IEnumerable<TService> GetAllInstances<TService>();
}

Service Locator дополняет библиотеку Prism методами расширения, показанными в следующем коде. Можно увидеть, что IServiceLocator используется только для разрешения, а не для регистрации.

public static class ServiceLocatorExtensions {
    public static object TryResolve(this IServiceLocator locator, Type type) {
        try {
            return locator.GetInstance(type);
        }
        catch (ActivationException) {
            return null;
        }
    }

    public static T TryResolve<T>(this IServiceLocator locator) where T: class {
        return locator.TryResolve(typeof(T)) as T;
    }
}

Метод расширения TryResolve, который контейнер Unity не поддерживает, возвращает экземпляр типа, который должен быть разрешён, если он было зарегистрирован, иначе он возвращает null.

ModuleInitializer использует IServiceLocator для того, чтобы разрешить зависимости модуля во время его загрузки, как показано в следующих примерах кода.

IModule moduleInstance = null;
try {
    moduleInstance = this.CreateModule(moduleInfo);
    moduleInstance.Initialize();
}
...

protected virtual IModule CreateModule(string typeName) {
    Type moduleType = Type.GetType(typeName);
    if (moduleType == null) {
        throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
    }
    return (IModule)this.serviceLocator.GetInstance(moduleType);
}

Соображения по использованию IServiceLocator


IServiceLocator не предназначается для использования в качестве контейнера общего назначения. У контейнеров может быть различная семантика использования, которая часто влияет на выбор контейнера. Принимая это во внимание, Stock Trader RI использует контейнер внедрения зависимости непосредственно вместо того, чтобы использовать IServiceLocator. Это является рекомендованным подходом при разработке приложений.

В следующих ситуациях использование IServiceLocator является уместным:
  • Вы — независимый поставщик программного обеспечения (ISV), разрабатывающий стороннюю службу, которая должна поддерживать различные контейнеры.
  • Вы разрабатываете службу, которая будет использоваться в организации, где используются различные контейнеры.

Дополнительная информация


Для получения информации, связанной с DI контейнерами, смотрите:
Перевод: microsoft patterns & practices
Николай Фёдоров @Unrul
карма
61,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (28)

  • –1
    MEF не полноценный контейнер. Рассматривать его как альтернативу не совсем корректно. К тому же с ним не удобно работать когда у вас несколько инстансов одного моудля. Про тестирование вообще молчу.
    • 0
      MEF вполне полноценный контейнер, решающий конкретный класс композиционных задач.
      В чем Вы видите его неполноценность?
      • 0
        MEF это фреймворк для разработки плагинов, причем более ориентированный на десктопные приложения.
        1. Тестируете как с MEF?
        2. Лайфтайм объектов?

        Рекомендую почитать msdn.microsoft.com/en-us/magazine/gg650670.aspx
        • 0
          1. А что не так с тестированием, при использовании MEF?
          2. Ну, по умолчанию, MEF сохраняет ссылки на созданные объекты, но это легко можно изменить.
          • 0
            1. Как вы тестовый контейнер собираете?
            2. Сохранение ссылок это даже не смешно ;) Управление жизненным циклом объекта это одна из основных задач IoC контейнера. Instance per HttpRequest? А на вызов в WebApi или WCF? А вложенные области видимости или вложенные контейнеры? А Instance per tenant? В статье справедливо заметили, что часть функционала пересекается, но при этом эти фреймворки решают разные задачи. Если нужна только самая базовая инъекция зависимостей без всего, то можно использовать MEF как IoC. Но шаг вправо или влево — и он уже становится совершенно не пригодным для этой цели.
            • 0
              1. В unit-тестах контейнер не нужен, а для интеграционных, можно использовать тот же TypeCatalog при настройке контейнера.
              2. О целях довольно хорошо говорит его название (Managed Extensibility Framework). :)
              • 0
                1. 0_о? Как код то выглядит? Видел реализации с конструктором без аргументов, в котором используется SatisfyImportsOnce, и для тестов — отдельный конструктор с зависимостями. Получается весьма уродливый код, где в каждом классе сидит ссылка на статичный контейнер.

                2. Ну изначальный посыл и был в тему того, что это MEF, а не IoC. А в статье автор частенько ставит между ними знак равенства, что может немного поломать мозги тем, что только начинает увлекательное путешествие ;)
                • 0
                  Эээ… А в чём проблема?

                      public interface IStrategy {
                          int Algorithm(int num);
                      }
                  
                      public sealed class Strategy : IStrategy {
                          public int Algorithm(int num) {
                              return num*2;
                          }
                      }
                  
                      public sealed class Calculator {
                          private readonly IStrategy _strategy;
                  
                          public Calculator(IStrategy strategy) {
                              _strategy = strategy;
                          }
                  
                          public int Calculate() {
                              return _strategy.Algorithm(42);
                          }
                      }
                  
                      public static class Program {
                          private static void Main(string[] args) {
                              var rb = new RegistrationBuilder();
                              rb.ForTypesDerivedFrom<IStrategy>().Export<IStrategy>();
                              rb.ForType<Calculator>().Export();
                  
                              var cc = new CompositionContainer(
                                  new AggregateCatalog(new AssemblyCatalog(typeof (Program).Assembly, rb)));
                  
                  
                              var calc = cc.GetExportedValue<Calculator>();
                  
                              Console.WriteLine(calc.Calculate());
                              Console.ReadLine();
                          }
                      }
                  

                  Потом тестим

                      [TestMethod]
                      public void TestMethod() {
                          var calc = new Calculator(
                              Mock.Of<IStrategy>(s => s.Algorithm(It.IsAny<int>()) == 42)
                              );
                          calc.Calculate().Should().Be(42);
                      }
                  
                  • 0
                    cc.GetExportedValue(); Вот в этом месте проблема. Вы все объекты так получаете?

                    var builder = new ContainerBuilder();
                    builder.RegisterInstance(Mock.Of(s => s.Algorithm(It.IsAny()) == 42)).As();
                    builder.RegisterType().AsSelf()
                    var container = builder.Build();

                    [TestMethod]
                    public void TestMethod()
                    {
                    var calc = Container.Resolve();
                    calc.Calculate().Should().Be(42);
                    }

                    PS. Извиняюсь, мне уже отрубили тэги и парсер кушает дженерики :)
                    • 0
                      На мой взгляд, GetExportedValue должен быть упрятан в глубине инфраструктуры и не появляться в обычных классах приложения, как и другие ссылки на контейнер. С Unity, к примеру, единственное место, где появляется IUnityContainer, это класс модуля (в Prism). Во всех остальных местах в конструктор инжектируются уже необходимые интерфейсы. С MEF даже это становится не нужным, модули регистрируют свои типы автоматически при их загрузке. В unit-тестах, идея использования контейнера вообще очень сомнительна. Все необходимые стабы и моки создаются и инжектируются вручную в эти самые конструкторы. Собственно, для чего это всё и задумывалось.
                      • 0
                        А wireup то как? Допустим в MVC контроллер? Это все равно натягивание одного на другое. MEF — для плагинов и очень базовой композиции, IoC — для всего остального. И то, при определенных требованиях, MEF не подходит. Просто не надо считать его за полноценный контейнер и сравнивать с Autofac, Windsor, StructureMap и на худой конец Unity.
                        • 0
                          Передать ссылку на CompositionContainer в активатор контроллеров и через него их ресолвить, как и с обычным контейнером. Проблем-то?
                          • 0
                            А лайфтайм то как контролировать? Это все самые базовые сценарии. Мы не касаемся еще интерсепторов и тд. С тем же успехом можно и просто везде сервис локатор вкрутить, но вот только он покроет 10% юзкейсов, которые могут возникнуть в сложном приложении. Ради интереса посмотрите Orchard Project и как используется IoC на полную катушку. MEF — только базовая композиция. У него есть свои плюшки, и его вполне можно использовать с IoC контейнером одновременно. Это косяк MS, что они представили в фреймворке технологию, которая частично перекрывает юзкейсы других технологий, но при этом решает совершенно другие задачи.
                            • +1
                              Подключился поздно, поэтому начну заново — MEF вполне полноценный контейнер, решающий конкретный класс композиционных задач. Если нужно делать хитрые манипуляции c лайфтаймом и перехватывать все этапы построения — юнити, конечно, подходит лучше. Если важнее наглядное, простое и декларативное управление композицией — то лучше, наверное, все-таки меф.

                              Ну и по вопросам — тестируется все прекрасно, тестируемые объекты складываются в тест-контейнер, дополняются заглушками того, что тестить не надо. Реализация IoC вполне законченная, но на своей практике я встречал людей, которым очень хочется использовать контейнер как фабрику на все случаи жизни и которые требуют от него кучу синтаксического сахара и возможностей, не связанных с композиционным IoC. А это приводит итоге к большой неразберихе.

                              Еще раз отрезюмирую — для использования с Prism MEF, как композиционный контейнер, подходит едва ли не лучше Unity. Уж точно не хуже. В общем случае надо рассматривать конкретные задачи.
                              • 0
                                Еще раз: IoC контейнер решает 3 основных задачи — Композиция, Управление жизненным циклом, и, в большинстве своем, поддерживают механизмы перехвата вызова и динамическое проксирование. Выше я привел статью на MSDN где подробно разъясняют что из себя представляет MEF. И, как я написал выше, для простого построения графа объектов в десктопном приложении — MEF подойдет, но при условии что у вас только базовые сценарии использования композиции. Так как MEF без костылей и жуткого колдунства решает только первую задачу, он не является полноценным IoC контейнером и сравнивать его с Autofac, Windsor, StructureMap не корректно, так как они решают разные задачи.
                                А про Unity я согласен — лучше MEF чем Unity ;-)

                                Приведу цитату (извиняюсь что без тегов):

                                In a nutshell, it’s correct to say that the functionality of the MEF and of a typical IoC framework overlap, but don’t coincide. With most IoC frameworks you can perform tasks that the MEF just doesn’t support. You could probably employ a functionally rich IoC container and, with some effort on your own, emulate some MEF-specific capabilities.
                                • 0
                                  Перехват вызовов — это не задача сама по себе, а способ решения какой-либо другой задачи.
                                  MEF обеспечивает внедрение зависимостей (dependency injection) и это именно та характеристика, которая ассоциируется как определяющая для IoC контейнеров, если судить по статьям на хабре и вики. :) Остальное — это уже фантазия тех, кто реализует контейнер. То, что контейнеру можно приделать упомянутый выше перехват не значит, что это обязательная необходимость для IoC контейнера.
                                  Жизненным циклом MEF управляет (т.е. самостоятельно создает и уничтожает объекты определенным политикой образом). И никакого жуткого колдунства и костылей не требуется, чтобы управлять жизненным циклом собственными политиками — хотя писать придется, да.

                                  Таким образом, MEF является вполне полноценным контейнером, также как МАЗ является полноценным автомобилем, пусть и не очень подходящим под то, чтобы на нем на работу ездить.

                                  Статья на MSDN кстати не утверждает, что MEF это не IoC-контейнер. Она говорит, что большинство IoC-фреймворков обладают более богатой функциональностью и можно, немножко дописав, реализовать функциональность MEF (равно как вы утверждаете, что, дописав MEF, можно реализовать функциональность этих фреймворков).

                                  А в целом, за исключением терминологического спора о том, IoC-контейнер ли MEF или нет, мы с вами сходимся во мнении, что задача определяет использование или неиспользование этого самого MEF и с точки приложения к этой задаче сравнивать его можно с любыми другими контейнерами. :)
                                  • 0
                                    Там как раз указывается что MEF не обладает той функциональностью, которая есть у большинства IoC контейнеров, и никогда не будет реализована в MEF =)
                                    Когда вышел первый релиз Spring.Net — просто DI было круто и достаточно, но сейчас требования к контейнерам совершенно другие.

                                    Лайфтайм менеджмент в MEF? Как уже выше писал — HttpRequest,WebApiRequest, OperationContext, UnitOfWork — как это все поддерживать? А AOP тоже весьма распространен в разработке серверного ПО. Как это делать с MEF? MEF — это плагины, обновляемые в рантайме каталоги и тд. Это круто, но когда надо автоматически инжектить логгер с именем класса и с возможностью оверрайда этого имени или использовать ленивую инициализацию зависимостей из текущего скоупа выполнения в single instance объект — MEF уже не крут, а возможность делать такие вещи должен предоставить контейнер.

                                    А так да. в десктопном приложении скорее всего MEF. На сервере… видел прототип и как потом его переписывали на Autofac в срочном порядке. Хотя для mission critical софта с длительным циклом поддержки версии я вообще буду использовать свой сервис локатор и сокращать все внешние зависимости :-)

                                    • 0
                                      но когда надо автоматически инжектить логгер с именем класса и с возможностью оверрайда этого имени — MEF уже не крут,

                                      Я делал это в MEF. Вполне удобно. Принцип в создании nonshared логгирующего агента и в присвоении агенту имени в событии после импортов. Три понятных строчки кода.
                                      использовать ленивую инициализацию зависимостей из текущего скоупа выполнения в single instance объект — MEF уже не крут

                                      Ну это вообще мне непонятно. Физически один объект разделяется между скопами и каждый скоп хочет видеть в нем свои зависимости?
                                      Если просто разделение объектов между областями — то это в MEF есть.
                                      Лайфтайм менеджмент в MEF?

                                      Думаю о том, чтобы написать по этому поводу статью. Почему бы нет? С созданием и областью видимости проблем не очень много, с ликвидацией проблем больше, хотя последнее, насколько мне известно, проблема не только MEF.

                                      Лично мне нравится то, что MEF не дает собой оперировать как универсальной фабрикой — как, наверное, видно из пред-пред. поста, я считаю это плохой практикой. :)
                                      • 0
                                        Приведите пример?=) А как быть с тем что

                                        Ну контейнер то как раз и создан, что бы быть универсальной фабрикой=) Под нужды.
                                        Допустим скользкий момент. когда у вас есть объект с жизненным циклом, равным жизненному циклу контейнера, но вам необходимо в нем получать доступ к объектам, которые имеют более короткий жизненный цикл. И кстати, с ликвидацией то же проблем не будет в нормальном контейнере.

                                        Ну и без AOP вообще не вариант уже жить=) Ну и самое главное, это то, что для MEF необходимо в свой код тащить специфичные для контейнера атрибуты. То есть человек, который использует мой фреймворк вообще не знает, что надо что-то там экспортить и тд. Он просто реализует интерфейсы, а я, в свою очередь, могу ему и логгеры разные давать и доп. сервисы инджектить и сам управлять жизенным циклом объекта(на весь инстанс сервера, на тэнант, на сессию, на запрос). Причем для этого еще могу и контролировать, что он себе инджектит, обвесть это проксями для кеша, аудита, логирования с моей стороны и тд.

                                        Допускаю, что при определенном рвении можно над MEF нарисовать это все, но зачем? Для этого уже есть инструменты, которые на голову превосходят его в этом.

                                        Напишите статью, там и обсудим. Я может то же соберусь с силами и по Autofac напишу. =)
                                        • 0
                                          И кстати, с ликвидацией то же проблем не будет в нормальном контейнере.

                                          Да будет, когда неясно, когда и кому объект ликвидировать.
                                          Ну контейнер то как раз и создан, что бы быть универсальной фабрикой=)

                                          Ну вот совершенно не согласен. Я считаю, что смешение в контейнере объектов сервисной категории и объектов, представляющих данные, крайне неполезно. Т.е. не очень хорошо хранить в контейнере соединения с базой, а вот класс, предоставляющий соединения, который композиционно всякое использует — нормально. С таким подходом у меня ограничений даже с мефом не возникало.
                                          Он просто реализует интерфейсы, а я, в свою очередь, могу ему и логгеры разные давать и доп. сервисы инджектить

                                          Самое страшное в AOP в неумелых руках (я не имею в виду вас) — совершенно неочевидные и, подчас, конфликтные инъекции. Собственно, неконтролируемое смешивание всего и вся, как мне кажется, и есть самое серьезное ограничение подобного подхода — ну типа как множественное наследование, только еще менее очевидное. Продуманная архитектура, практически устраняет необходимость в широком применении AOP. А сделать инъекцию со своим временем жизни вполне возможно, ну это уже в статью пойдет. Обвешивать проксями и прочим без сложностей тоже можно, при условии, что написанный пользователем класс позволяет подобные паттерны, и это смотрится вполне естественно в MEF…

                                          Ну и самое главное, это то, что для MEF необходимо в свой код тащить специфичные для контейнера атрибуты.

                                          Совершенно необязательно. Есть класс RegistrationBuilder.
                                          Допускаю, что при определенном рвении можно над MEF нарисовать это все, но зачем?
                                          Многия знания, как говорится, рождают многия печали. :) Обилие возможностей приводит к каше на проекте, когда сложно разобраться, какой объект как получается и что из себя представляет (это, конечно, на больших проектах и в команде чувствуется).
                                          Плюсы мефа я уже указал — простой в использовании, наглядный и надежный вроде как фреймворк для сбора композиции. Зачем пилить полено лазером, если можно пилой?
                                          • 0
                                            Соединение с базой в чистом виде вообще вредно просто так хранить в своем коде, а вот Сессии Nhibernate или контексты EF запросто. Причем как раз можно реализовать и менеджмент жизненного цикла, и контроль зависимостей.
                                            А еще абстрактные репозитории, автоматическая инъекция декораторов и стратегий? ;-)

                                            Согласен, AOP и продвинутые практики DI резко повышают планку. Но есть решения как контролировать sanity контейнера и разработчика. Например в Autofac механизм LifetimeScope. Как раз для того, что бы разработчик не смог заресолвить PerRequest в SingleInstance. Тот же аудит, контроль доступа к методу, и даже инджект tenantId как пример=)

                                            Здесь все зависит от сложности проекта, размера команды, и организации процесса=). Мы уже перешли на то, что все компоненты в проектах — Nuget пакеты, стандартизировали подход к DI, и ни у кого не вызывает вопросов. Джуниоры, допустим просто знают что им можно объявить свойство ILogger и им пользоваться. Выход за эти рамки им не нужен.

                                            Хотя сейчас в некоторых местах я просто возвращаюсь к сервис локатору, так как более низкоуровневые моменты можно элегантно решать только с ним(особенно всякие веселые штуки с LDAP и деревьями). И там IoC не только не нужен, а вреден, так как усложняет работу с доменными моделями, постоянно подталкивая к анемичности оных.

                                            • 0
                                              Вот основная моя мысль в чем, которую я пытаюсь донести — вместо того, чтобы думать, как красиво реализовать декораторы и стратегии, лучше думать о том, как обойтись без них. Для этого придумана аббревиатура KISS, например. :) И полнофункциональные в своей области, но не имеющие ничего лишнего за ней средства очень этому способствуют. Зачем думать о том, как контролировать разработчиков, чтобы они в сложности не запутались и не наизобретали лишнего, если можно в принципе этой сложности не допускать?
                                              Опасность опять же шаблонов в том, что, увлекшись кодом, можно забыть о том, что за ним находится.
                                              Причем здесь MEF? :) Он и есть полнофункциональное средство в своей области без всякого лишнего.
                                              • 0
                                                А их все равно необходимо контролировать, либо писать тонны повторяющегося кода, кто опять же плохо. Вы забываете про принцип DRY =) И можно нарисовать кучу репозиториев и сервисов и прослоек, либо использовать более сложные техники, но исключить дублирование. MEF — тут не совсем помогает.
                                                • 0
                                                  Да меф тут не при чем. С точки зрения неповторяемости он помогает так же, как и все остальные контейнеры, только в каких-то случаях (для которых в нем не предусмотрено готового сценария) будет больше кода. Зато в других, за счет того, что в нем меньше возможностей и, соответственно, конфигураций, кода будет меньше.
                                                  В каких-то ситуациях будет разумно вообще контейнер не использовать.
                                                  • 0
                                                    «Зато в других, за счет того, что в нем меньше возможностей и, соответственно, конфигураций, кода будет меньше.»

                                                    Не совсем понял, как меньшие возможности ведут к уменьшению кода?
                                                    • 0
                                                      Если у меня есть супернавороченная универсальная система, которая может все, но которую надо сконфигурировать, то для решения задачи, которая является родной для более простой системы, решающей непосредственно эту задачу, кода надо писать больше.
                                                      Грубо говоря, чем больше у ножика лезвий, тем тяжелее найти нужное. Но это мы уже вообще в глубокую философию ушли. :)
                                                      По теме же советую посмотреть на возможности MEF 2 (работа с обобщениями, RegistrationBuilder, дочерние контейнеры). Разумеется, он по-прежнему аскетичен, по сравнению с тем же автофаком например, но, как мне кажется, в 80% приложений его хватит за глаза.
                                                      • 0
                                                        Надо знать что делаешь=) Это самое главное.
                                                        Для десктопа — за глаза хватит MEF. Там зачастую нет таких проблем как на сервере.

                                                        Кстати, надеюсь парни из MS не будут сильно стучать по голове… посмотрите как TFS2012 написан ;) у них код не обфусцирован… Интересное чтиво.
            • 0
              В MEF есть дочерние контейнеры (если вы это имели в виду под вложенностью).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.