Рекомендации IoC, Dll и сборка

Хотя этот вопрос связан с StructureMap, мой общий вопрос:

При подключении компонентов с помощью IoC контейнер в коде (в отличие от для настройки через xml), вы обычно требуется явный проект/сборка ссылки на все сборки?

Почему отдельные сборки? Потому что:


"Абстрактные классы, находящиеся в отдельная сборка из их бетона реализации - отличный способ добиться такого разделения". - Рамки Руководства по проектированию стр .91


Пример:

Скажем, у меня есть PersonBase.dll и Bob.dll

Боб наследуется от абстрактного класса PersonBase. Они оба находятся в пространстве имен Person. Но в разных сборках.

Я программирую PersonBase, а не Боб.

В моем основном коде мне нужен человек. StructureMap может сканировать сборки. Отлично, я попрошу StructureMap для одного!

Теперь, в моем основном коде, я, конечно, ссылаюсь только на PersonBase, а не на Боба. На самом деле я не хочу, чтобы мой код ничего не знал о Бобе. Нет ссылок на проект, нет нутин. Это все.

Итак, я хочу сказать:

//Reference: PersonBase.dll (only)
using Person;  
...

//this is as much as we'll ever be specific about Bob:
Scan( x=> { x.Assembly("Bob.dll"); }

//Ok, I should now have something that a PersonBase (Bob). But no ?
ObjectFactory.GetAllInstances<PersonBase>().Count == 0

Не повезло. Что делает работа явной, что я хочу Боба:

//Reference: PersonBase.dll and Bob.dll
using Person; 
...
Scan( x => {x.Assembly("Bob.dll"); }

//If I'm explicit, it works. But Bob just a PersonBase, what gives?
ObjectFactory.GetAllInstances<Bob>().Count == 1 //there he is!

Но теперь мне пришлось ссылаться на Bob.dll в моем проекте, чего я не хотел.

Я могу избежать этой ситуации, используя конфигурацию Spring + Xml. Но затем я вернусь к конфигурации Spring + Xml...!

Я что-то пропустил с помощью StructureMap или как общий принцип, делать (свободно) IoC конфигурациям нужны ссылки для всех сборок?

Возможно, связанный с этим вопрос: StructureMap и сборки сканирования

Ответ 1

Наконец-то я выяснил это. Это выглядит так:

IoC Uml http://img396.imageshack.us/img396/1343/iocuml.jpg

с сборками

  • Core.exe
  • PersonBase.dll(ссылка время компиляции от Core.exe)
  • Bob.dll( загруженное время выполнения через StructureMap Scan)
  • Betty.dll( загруженное время выполнения через StructureMap Scan)

Чтобы получить его с помощью StructureMap, мне понадобился пользовательский "ITypeScanner" для поддержки сканирования для сборок:

public class MyScanner : ITypeScanner {
  public void Process(Type type, PluginGraph graph) {

    if(type.BaseType == null) return;

    if(type.BaseType.Equals(typeof(PersonBase))) {
      graph.Configure(x => 
        x.ForRequestedType<PersonBase>()
        .TheDefault.Is.OfConcreteType(type));
    }
  }
} 

Итак, мой основной код выглядит так:

ObjectFactory.Configure(x => x.Scan (
  scan =>
  {
    scan.AssembliesFromPath(Environment.CurrentDirectory 
    /*, filter=>filter.You.Could.Filter.Here*/);

    //scan.WithDefaultConventions(); //doesn't do it

    scan.With<MyScanner>();
  }
));

ObjectFactory.GetAllInstances<PersonBase>()
 .ToList()
  .ForEach(p => 
  { Console.WriteLine(p.FirstName); } );

Ответ 2

Вы также можете настроить xml с помощью StructureMap. Вы даже можете их смешивать, если хотите.

Также есть атрибуты StructureMap, которые вы можете поместить в свой класс Bob, чтобы сообщить StructureMap, как загрузить сборку. DefaultConstructor - это тот, который я время от времени использую.

Ответ 3

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

ObjectFactory.Initialize(initialization => 
   initialization.ForRequestedType<PersonBase>()
    .TheDefault.Is.OfConcreteType<Bob>());

Ответ 4

Что мы делаем в моем текущем проекте (который использует AutoFac, а не StructureMap, но я думаю, что это не должно меняться):

У нас есть интерфейсы, определяющие внешние службы, которые приложение использует в основной сборке, скажем App.Core (например, ваш PersonBase).

Затем мы реализуем реализации этих интерфейсов в Services.Real (например, Bob.dll).

В нашем случае мы также имеем Service.Fake, которые используются для облегчения тестирования пользовательского интерфейса с зависимостями от других корпоративных служб и баз данных и т.д.

Само внешнее приложение "клиент" (в нашем случае приложение ASP.NET MVC) ссылается на App.Core.

Когда приложение запускается, мы используем Assembly.Load для загрузки соответствующей DLL-реализации "Службы" на основе настройки конфигурации.

Каждая из этих DLL имеет реализацию IServiceRegistry, которая возвращает список служб, которые он реализует:

public enum LifestyleType { Singleton, Transient, PerRequest}

public class ServiceInfo {
    public Type InterfaceType {get;set;}
    public Type ImplementationType {get;set;}
    // this might or might not be useful for your app, 
    // depending on the types of services, etc.
    public LifestyleType Lifestyle {get;set;} 
}

public interface IServiceRegistry {
    IEnumerable<ServiceInfo> GetServices();
}

... приложение находит этот ServiceRegistry через отражение и перечисляет через эти экземпляры ServiceInfo и регистрирует их в контейнере. Для нас этот регистр-все-службы проживает в веб-приложении, но это возможно (и, во многих случаях, предпочтительнее) иметь его в отдельной сборке.

Таким образом, мы можем изолировать логику домена от кода инфраструктуры и предотвращать "просто-один раз", когда приложение заканчивается в зависимости от прямой ссылки на код инфраструктуры. Мы также избегаем иметь ссылку на контейнер в каждой реализации Службы.

Одна действительно важная вещь, если вы это делаете: убедитесь, что у вас есть тесты, которые подтверждают, что вы можете создать каждый тип "верхнего уровня" (в нашем случае, ASP.NET MVC Controllers) с каждой потенциальной конфигурацией IOC контейнер.

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