IoC/SRP дизайн проблема
Я разрабатываю систему, которая предоставляет веб-сервис для доступа к своей базе данных и предоставляет его через классы.NET. Наш обычный способ работы - создавать экземпляр класса веб-службы всякий раз, когда нам нужно получить доступ к базе данных и напрямую использовать этот экземпляр; это, конечно, полностью идет вразрез с IoC и создает в основном непроверяемый код. Сейчас я пытаюсь разработать новый стандартный способ работы с использованием IoC, чтобы позволить нам писать (больше) твердотельный код.
Мое текущее решение таково (не очень хорошо объяснено):
Веб-сервис обернут в DatabaseConnection
класс, который хранит объект веб-службы в качестве защищенного члена и предоставляет доступ к ряду часто используемых общих вызовов базы данных.
Когда мне нужен доступ к базе данных в реальном приложении, я наследую этот класс (вызывая новый класс, скажем, ApplicationDatabaseConnection
) и реализовать необходимые взаимодействия с базой данных в методах, которые затем могут вызывать веб-службу.
Этот класс не используется непосредственно в приложении, но предоставляет интерфейсы "соединителя" для различных частей приложения, каждая из которых представлена моделью контроллера / представления, подобной классу на верхнем уровне. Всякий раз, когда вызывается одна из этих функций приложения (например, из пользовательского интерфейса), создается соответствующий объект контроллера и передается мой ApplicationDatabaseConnection
объект как реализация соответствующего интерфейса "соединителя", поэтому, насколько я могу судить, доступ к базе данных должным образом инкапсулирован и отделен в этой точке.
Моя проблема заключается в следующем: хотя это первый случай, когда я нашел фактическое использование ISP (принцип разделения интерфейса) в своем собственном коде (хотя я далеко не уверен, действительно ли это разумное использование концепции), я боюсь, что этот класс может сделать слишком много и нарушить SRP (принцип единой ответственности). Или "только предоставление доступа к базе данных, хотя и для нескольких разных потребителей" является одной обязанностью?
Чтобы сделать это немного яснее, вот как выглядят соответствующие классы:
DatabaseConnection
protected m_webservice
public OftenUsedDatabaseAccess()
ApplicationDatabaseConnection : DatabaseConnection, IConnectorA, IConnectorB
public IConnectorA.RetrieveRecords(fieldValue)
public IConnectorB.WriteStuff(listOfStuff)
FunctionAController
private m_IConnectorA
FunctionBController
private m_IConnectorB
Альтернативы, которые я могу придумать, мне тоже не кажутся идеальными.
Чтобы разделить функциональность доступа к базе данных, ApplicationDatabaseConnection
класс может быть только фабричным классом с Create
методы для разных разъемов (за IConnectorAFactory
, IConnectorBFactory
интерфейсы) - но на самом деле нет ничего, что могло бы потребовать заводской шаблон; нет ничего, что я знаю только при создании экземпляров объектов "контроллер".
Кроме того, фактические классы соединителей по существу также должны быть производными от DatabaseConnection
потому что им нужны одни и те же базовые способности, и тогда (самое позднее) вся конструкция становится довольно зловещей.
Я полагаю, что в какой-то момент в своем мышлении я сделал неправильный поворот и теперь полностью на неправильном пути. Как должна выглядеть структура такого решения? Любой толчок в правильном направлении будет с благодарностью.
Редактировать:
Ответ @Tobias заставил меня осознать, что я забыл важную деталь: есть две разные версии веб-сервиса с почти одинаковыми возможностями, но совершенно разными API, что является одной из причин, по которым его нужно заключать в капсулу.
Еще один из них заключается в том, что если мои логические классы обращаются к веб-службе напрямую (или через интерфейс), они также занимаются построением запросов веб-службы во всех их деталях, что делает гораздо более тесно связанный код (таким, каким мы были). до сих пор) и нарушает SRP - отсюда и идея интерфейса соединителя.
2 ответа
Много месяцев спустя я знаю, что подход "ApplicationDatabaseConnection" не только нарушает SRP, но и OCP, в то же время препятствуя модульности.
Маршрут, который я в итоге выбрал, был немного похож на альтернативу "фабрики", которую я описал - "основной" объект DatabaseConnection относится к подклассу, который также имеет метод "Create", который принимает фабрики для конкретных производных DatabaseConnection и не заботится о что именно это создает. Таким образом, каждый контроллер может запросить свой собственный объект подключения, и можно добавить контроллеры, о которых основное приложение не знает (например, через MEF).
Почему бы просто не создать интерфейс, такой как MyWebService, и внедрить его в MyWebServiceImpl, который вызывает ваш сервис? Могли бы выставить те же методы, что и ваш сервис, и просто делегировать ему...