#c# #c#-4.0 #dependency-injection #inversion-of-control
#c# #c #-4.0 #внедрение зависимостей #инверсия управления
Вопрос:
У меня такая структура проекта :-
CentralRepository.BL
CentralRepository.BO
CentralRepository.DataAccess
CentralRepository.Tests
CentralRepository.Webservices
и между ними существует очень много зависимостей. Я хочу использовать Unity для уменьшения зависимостей, поэтому я собираюсь создавать интерфейсы для своих классов. Мой вопрос в том, в каком проекте должны находиться интерфейсы. Я думаю, что они должны быть на уровне BO. Может кто-нибудь дать мне некоторые рекомендации по этому поводу, пожалуйста
Комментарии:
1. а что такое BO? Бизнес-объекты? В конце концов, это не имеет значения — держите интерфейсы близко к объектам. Кстати: Unity (или любой IOC-контейнер) не уменьшит зависимости, но поможет вам управлять ими
Ответ №1:
На комбинаторном уровне у вас есть три варианта:
- Определение интерфейсов в отдельной библиотеке
- Определение интерфейсов вместе с их потребителями
- Определение интерфейсов вместе с их разработчиками
Однако последний вариант — действительно плохая идея, потому что он тесно связывает интерфейс с разработчиком (или наоборот). Поскольку весь смысл введения интерфейса в первую очередь заключается в уменьшении связи, это ничего не дает.
Часто бывает достаточно определить интерфейс вместе с потребителем, и лично я делаю дополнительный шаг по определению интерфейса в отдельной библиотеке только тогда, когда в игре участвуют разные потребители (что в основном происходит, если вы отправляете общедоступный API).
Комментарии:
1. Ничто не говорит о том, что реализация по умолчанию должна быть общедоступной. Если все, что вы предоставляете, — это интерфейсы, то сохранение реализаций по умолчанию близко работает хорошо (при условии, что наличие значения по умолчанию имеет смысл, например, для DTO).
2. @Mark Seemann итак, является ли клиент классом, который наследуется от интерфейса? Я предполагаю, что так
3. Потребитель — это класс, который использует интерфейс… Является ли разработчик общедоступным или нет, не имеет ничего общего с тем, как связаны типы.
Ответ №2:
BO — это, по сути, объекты вашего домена, или, по крайней мере, это мое предположение. В общем, если вы не используете шаблон, подобный ActiveRecord, они являются только объектами состояния. С другой стороны, интерфейс определяет поведение. Из многих «лучших практик» не очень хорошая концепция смешивать поведение и состояние. Теперь я, вероятно, буду немного блуждать, но я думаю, что фон может помочь.
Теперь к вопросу о том, где должны существовать интерфейсы. Есть несколько вариантов.
- Вставьте интерфейсы в библиотеку, к которой они принадлежат.
- Создайте отдельную библиотеку контрактов
Проще всего поместить их в одну и ту же библиотеку, но тогда ваши макеты полагаются на библиотеку, а также на ваши тесты. Не так уж много, но у него есть небольшой запах.
Мой обычный метод заключается в настройке подобных проектов:
{компания}.{программа / проект}.{проблема (необязательно)}.{область}.{подобласть (необязательно)}
Первые два-три бита имени в вашем названии покрыты словом «Центральное хранилище». В моем случае это была бы MyCompany.Центральный репозиторий или MyCompany.Моя программа.Центральное хранилище, но соглашение об именах не является основной частью этого поста.
Разделы «области» являются основной темой этого поста, и я обычно использую следующее.
- Настройте библиотеку объектов домена (ваш BO): CentralRepository.Domain.Модели
- Настройте библиотеку исключений домена: CentralRepository.Domain.Exceptions
- Все / большинство других проектов ссылаются на два вышеупомянутых, поскольку они представляют состояние в приложении. Конечно, ВСЕ бизнес-библиотеки используют эти объекты. Библиотека (библиотеки) сохранения может иметь другую модель, и у меня может быть модель представления в библиотеке (библиотеках) опыта.
- Далее настройте основную библиотеку: центральное хранилище.Ядро (может иметь дочерние области?). вот где лежит бизнес-логика (фактическое применение, поскольку изменения в постоянстве и опыте не должны влиять на основные функциональные возможности).
- Настройте тестовую библиотеку для ядра: CentralRepository.Core.Test.Unit.VS (у меня есть Unit.VS, чтобы показать, что это модульные тесты, а не интеграционные тесты с библиотекой модульных тестов, и я использую VS для указания MSTest — у других будут разные имена).
- Создайте тесты, а затем настройте бизнес-функциональность. При необходимости настройте интерфейсы. Пример
- Нужны данные из DAL, поэтому интерфейс и макет настроены для использования данных для основных тестов. Имя здесь будет что-то вроде CentralRepository.Сохраняется.Контракты (также могут использовать подобласть, если существует несколько типов сохраняемости).
Основной концепцией здесь является «Ядро как приложение», а не n-уровневый (они совместимы, но, думая только о бизнес-логике, как о парадигме, вы слабо связаны с настойчивостью и опытом).
Теперь вернемся к вашему вопросу. Способ настройки интерфейсов основан на расположении «сопряженных» классов. Итак, я бы, вероятно,:
Центральный репозиторий.Ядро.Контракты Центральный репозиторий.Опыт.Обслуживание.Заключает контракты с центральным хранилищем.Сохраняется.Обслуживание.Заключает контракты с центральным хранилищем.Сохраняются.Данные.Контракты
Я все еще работаю с этим, но основная концепция заключается в том, что следует учитывать как мой IoC, так и тестирование, и я должен иметь возможность изолировать тестирование, что достигается лучше, если я могу изолировать контракты (интерфейсы). Логическое разделение — это нормально (одна библиотека), но я обычно так не поступаю из-за наличия по крайней мере пары зеленых разработчиков, которым трудно увидеть логическое разделение без физического разделения. Ваш пробег может отличаться. :-0
Надеюсь, это бессвязное изложение каким-то образом поможет.
Ответ №3:
Я бы посоветовал хранить интерфейсы везде, где в большинстве случаев находятся их разработчики, если вы говорите о сборках.
Лично я, когда использую многоуровневый подход, обычно даю каждому слою собственную сборку и даю ему ссылку на слой под ним. На каждом уровне большинство общедоступных объектов являются интерфейсами. Итак, на уровне доступа к данным у меня могут быть ICustomerDao и IOrderDao в качестве общедоступных интерфейсов. У меня также будут общедоступные фабрики Dao в сборке DAO. Затем у меня будут конкретные реализации, помеченные как внутренние — CustomerDaoMySqlImpl или CustomerDaoXmlImpl, которые реализуют общедоступный интерфейс. Затем общедоступная фабрика предоставляет реализации пользователям (т. Е. На уровне домена) без того, чтобы пользователи точно знали, какую реализацию они получают — они предоставляют информацию фабрике, а фабрика разворачивается и передает им ICustomerDao, который они используют.
Причина, по которой я упоминаю все это, заключается в том, чтобы заложить основу для понимания того, какими на самом деле должны быть интерфейсы — контракты между поставщиком услуг и клиентом API. Таким образом, с точки зрения зависимости вы хотите определить контракт в целом, где находится поставщик услуг. Если вы определяете его в другом месте, вы, возможно, на самом деле не управляете своими зависимостями с помощью интерфейсов, а вместо этого просто вводите бесполезный уровень косвенности.
В любом случае, я бы сказал, думайте о своих интерфейсах так, как они есть — контракт с вашими клиентами относительно того, что вы собираетесь предоставлять, сохраняя при этом в тайне детали того, как вы собираетесь это предоставлять. Вероятно, это хорошая эвристика, которая сделает более интуитивно понятным, где размещать интерфейсы.
Комментарии:
1. -1 Интерфейсы не являются контрактами: blogs.msdn.com/b/kcwalina/archive/2004/10/24/246947.aspx Все остальные утверждения в этом ответе также неверны на фундаментальных уровнях. Связывание интерфейса с реализацией полностью противоречит цели слабой связи.
2. Это похоже на семантические придирки. Блоггер прав в том, что «контракт», указанный интерфейсом (семантика API), не совпадает с требованием предварительного условия или гарантией постусловия, но это всего лишь вопрос степеней. Использование того факта, что они не являются одним и тем же, в качестве аргумента о том, что они не могут быть частью одного и того же, является логической ошибкой. Что касается той части вашего заявления, которая не является ссылкой на сообщение в блоге, я считаю ее расплывчатой, необоснованной и свидетельствующей о том, что вы невнимательно прочитали мой пост.
3. В качестве общего замечания я бы объяснил свой взгляд на интерфейсы и контракты, сказав, что интерфейс с описательным именем метода обеспечивает подразумеваемый контракт между вызывающим и отправителем. Время компиляции до / после условия и инвариантное принудительное исполнение (т. Е. Контракты Microsoft Code) обеспечивают принудительный контракт. Это вопрос степеней.