Рекомендации 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 контейнер.
В противном случае довольно легко забыть реализовать один интерфейс и разбить огромные разделы вашего приложения.