Ссылка на один и тот же интерфейс в разных сборках - PullRequest
4 голосов
/ 15 июля 2009

Я хочу реализовать архитектуру, включающую различные сборки .NET (т.е. модули). Некоторые из этих модулей должны предоставлять сервисы, которые используются в качестве интерфейсов .NET другими модулями. Модули должны быть загружены и зарегистрированы динамически во время выполнения, я не хочу иметь жестко закодированные зависимости между ними.

Пример:

  Module1.dll:
    Defines a class implementing interface IService1
  Module2.dll:
    Uses the class provided by Module1 through the interface IService1 

Проблема в том, куда поместить определение IService1: оба модуля нуждаются в этом определении. Но поскольку Module2 также должно работать при отсутствии Module1 (доступность службы проверяется во время выполнения), я не хочу, чтобы Module2.dll ссылался на Module1.dll напрямую.

Одна возможность состоит в том, чтобы разбить каждый модуль на две сборки (интерфейс и определение), но это будет означать, что я удваиваю количество библиотек DLL, которые мне не нужны.

Я подумал также об использовании отдельной "Interface Dll", то есть одной единственной сборки, содержащей все определения интерфейса, но, опять же, если я изменяю один единственный интерфейс или если я добавляю новые модули (с новыми интерфейсами), мне нужно обновить это центральная DLL и, следовательно, все остальные модули (так как все они зависят от нее ...)

Я хотел бы связать определение интерфейса с Module1 и Module2, но я не знаю, соотв. как это возможно.

Буду признателен за любые идеи

Редактировать Возможно, пример был слишком прост: может быть сценарий, в котором Module1a.dll, Module1b.dll и т. Д. Предоставляют реализации для IService, а Module2a.dll, Module2b.dll и т. Д. Используют их ...

Ответы [ 5 ]

2 голосов
/ 15 июля 2009

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

По сути, вы будете иметь одно и то же объявление интерфейса на обеих сторонах провода, а затем они будут автоматически подключены агентом, который является либо пользовательской реализацией Castle DynamicProxy, а также некоторыми компонентами Reflection или контейнером IoC, например Ninject , который поддерживает сервисные локаторы.


По сути, вы пишете «контракт» как для Module1.dll, так и для Module2.dll

public interface IFooProvider {
    void Foo GetFoo();
}

Затем вам понадобится реализация сервисного локатора, которая является центральным фрагментом кода, который поможет модулю зарегистрировать себя, и потребитель найдет зарегистрированный модуль. Это нормальная работа упомянутого выше контейнера DI или IoC.

Это будет выглядеть так или иначе так:

public interface IServiceLocator {

    object LocateProvider<ContractType>();

    void RegisterProvider<ContractType>(object implementation);
}

По сути, Module1 при загрузке должен зарегистрироваться в ServiceLocator вместе с контрактом, который он предоставляет, а затем Module2 при загрузке вызовет метод LocateProvider, предоставляющий требуемый контракт, и получит реализацию Module1.

Что-то стоит:

public class Module1Implementation : IProviderContract {
    void Foo GetFoo() { return new Foo(); }
}

public class Main {
    public void Main() {

        var locator = ServiceLocator.GetLocator();
        locator.RegisterProvider<IFooProvider>(new Module1Implementation());

    }
}

А в Module2.dll:

public class Consumer {

    public IFooProvider FooProvider { get; set; }

    public Consumer() {
        var locator = ServiceLocator.GetLocator();
        FooProvider = locator.LocateProvider<IFooProvider>();

        // if Module1.dll is loaded, the locator should supply 
        // Module1Implementation for you
    }
}

Тогда все «поставщики» модуля должны ссылаться на DLL локатора службы, которая должна представлять собой 1 дополнительную DLL, или она может быть встроена непосредственно в основное приложение. А при загрузке каждый модуль должен зарегистрироваться в ServiceLocator вместе с «контрактом», который они представляют.

Затем, последняя часть головоломки, это то, как вы делаете контракты похожими, которые определены в отдельных библиотеках, чтобы они выглядели одинаковыми, поэтому я упомянул Castle DynamicProxy и Reflection. Как это сделать - это уже не так давно, поэтому я оставлю это Google. : -)


Как это поможет? Все зависимости между всеми DLL, но реализация локатора службы удалены. Таким образом, ваша головная боль при развертывании уменьшена до:

  • Убедитесь, что реализация локатора службы надежна, поскольку это единственное, что не может быть легко изменено.
  • Убедитесь, что компоненты, которые должны взаимодействовать друг с другом, имеют один и тот же "контракт"
  • Если контракт меняется, используйте Фасад для обеспечения обратной совместимости.

Уф! Это было довольно длинно и модно, но я надеюсь, что это ответит на ваш вопрос.

1 голос
/ 15 июля 2009

Речь идет не о модулях и DLL, а о дизайне ОО.

Ожидается, что IService1 будет реализован рядом других модулей, или он уникален для Module1? Если вам нужно, чтобы несколько разных модулей реализовывали IServer1 без зависимости друг от друга, вам понадобится dll, содержащий этот интерфейс и любой из его вспомогательных файлов.

Это не удвоит количество dll, которое у вас есть, добавит только 1 дополнительный.

Так что либо определите ваш интерфейс в Module1.dll, либо в 3-й dll только для интерфейсов (контрактов).

Каждый раз, когда вам нужно иметь дело с несколькими модулями, у вас будет общий модуль, содержащий файлы ядра, которые все используют (интерфейсы, API и т. Д.). Модуль обычно не ссылается на другие модули.

0 голосов
/ 15 июля 2009

Что не так с помещением интерфейса в Module2.dll?

IService1 - это интерфейс (или контракт), определенный Module2.dll, который должен быть реализован в каждом классе, который должен использоваться Module2.dll.

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

0 голосов
/ 15 июля 2009

Один из распространенных подходов к этому состоит в том, чтобы иметь отдельную сборку для ваших «контрактных» интерфейсов, которая не содержит никаких деталей реализации, только определения интерфейса.

Module1 ссылается на CoreInterfaces.dll (или как вы его называете) Модуль2 ссылается на CoreInterfaces.dll

0 голосов
/ 15 июля 2009

Module2.dll должен объявить IService, а Module1.dll должен ссылаться на Module2.dll. Затем Module2.dll должен использовать отражение (возможно, с настройкой конфигурации), чтобы создать тип, объявленный в Module1.dll, и привести его к IService:

// in module2.dll
var serviceInstance = (IService)Activator.CreateInstance(typeToLoad);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...