Организация интерфейсов
Я просто читаю Agile Principles, Patterns and Practices в С# Р. Мартина и М. Мартина, и они предлагают в своей книге сохранить все ваши интерфейсы в отдельном проекте, например. Интерфейсы.
В качестве примера, если у меня есть проект Gui, который содержит все мои пользовательские классы Gui, я буду хранить их интерфейсы в проекте Interfaces. В частности, у меня был класс CustomButton в Gui, я бы сохранил интерфейс ICustomButton в интерфейсах.
Преимущество состоит в том, что любому классу, который нуждается в ICustomButton, не требуется ссылка на самого Gui, но только на проект с меньшим весом интерфейсов.
Кроме того, если класс в проекте Gui изменится и, следовательно, заставит его перестроить, только проекты, непосредственно ссылающиеся на CustomButton, нуждаются в перекомпиляции, тогда как те, которые ссылаются на ICustomButton, могут оставаться нетронутыми.
Я понимаю эту концепцию, но вижу проблему:
Предположим, что у меня есть этот интерфейс:
public interface ICustomButton
{
void Animate(AnimatorStrategy strategy);
}
Как вы можете видеть, это относится к AnimatorStrategy, который является конкретным классом и, следовательно, будет сидеть в другом проекте, позволяет называть его анимацией.
Теперь проект интерфейса должен ссылаться на анимацию. С другой стороны, если Animation использует интерфейс, определенный в интерфейсах, он должен ссылаться на него.
Циклическая зависимость - "Здесь мы приходим".
Единственное решение этой проблемы, которое я вижу, заключается в том, что все методы, определенные в интерфейсах, принимают входные данные, которые сами являются интерфейсами. Попытка реализовать это, скорее всего, будет иметь эффект домино, хотя и быстро требует, чтобы интерфейс был реализован даже для самых основных классов.
Я не знаю, хотел бы я справиться с этими накладными расходами при разработке.
Любые предложения?
Ответы
Ответ 1
Остерегайтесь всегда, никогда и никогда - особенно почти всех, ни одного, ни каждого.
Если вы всегда ставите все интерфейсы в отдельную сборку? Нет - не обязательно.
Должны ли вы вводить интерфейсы, которые вы ожидаете от внешних потребителей вашего кода, - возможно. Я бы поместил интерфейсы во внешнюю сборку, если вы ожидаете, что несколько сборок в вашем проекте будут опираться на них, - это может помочь нарушить взаимосвязи связей. Я также использовал эту практику для решения проблем с циклическими ссылками, где сборки должны знать интерфейсы друг в друге.
Я не устанавливаю интерфейсы, которые используются только внутри проекта в отдельной сборке. Я также не продвигаю интерфейсы в свою сборку, когда мои проекты относительно невелики - или интерфейсы не предназначены для использования без сборки, которая зависит от них.
Как и в примере, который вы представили, я бы предположил, что вы не считаете ссылки на классы в своей системе с интерфейсов. Когда это возможно, я стараюсь, чтобы интерфейсы ссылались только на другие интерфейсы - это держит вещи относительно развязанными. Вы не всегда можете добиться этого - и поэтому, когда у вас есть эти типы интерфейсов, вы должны удержать их со сборкой, на которой они зависят.
Если вы решите поместить интерфейсы в отдельную сборку, вы не обязательно должны помещать их в единую сборку. Вы можете разбить их по их предполагаемому использованию - таким образом, потребители могут использовать только интерфейсы, относящиеся к определенному домену функциональности.
Ответ 2
Простой ответ: AnimatorStrategy также должен быть интерфейсом. Реализация для AnimatorStrategy находится в проекте Animation, но интерфейс будет находиться в проекте AnimationInterfaces или интерфейсах (по мере необходимости).
У меня не обязательно был бы один проект интерфейса, но один для каждой функциональной группировки. Клиенты Animation будут ссылаться на AnimationInterfaces, а клиенты Gui будут ссылаться на GuiInterfaces.
Таким образом вы сохраняете свои контракты, не смешиваясь ни с какой реализацией.
Для чего я предпочитаю идти в другую сторону с соглашениями об именах. Поместите интерфейсы в анимацию и реализации в AnimationImpl (или аналогичные). Таким образом вы ссылаетесь на функциональное имя.
Ответ 3
Я думаю, что циклические зависимости могут быть неизбежными, даже если вы объявляете все как интерфейсы, потому что в сборке Foo может быть метод, который принимает параметр IBar и метод в сборке Bar, который принимает параметр IFoo.
Я бы сказал, что универсального рецепта для этого нет, но вместо этого вы должны использовать "здравый смысл" для разделения интерфейсов в сборках по мере необходимости.
Ответ 4
Как определить интерфейс с методами, использующими общие типы? Например.
public interface IThing
{
int DoMyThing<T>(T variable);
}
Если вам нужны ограничения на T variable
, вы можете сделать:
int DoMyThing<T>(T variable) where T : IOtherInterface;
Где IOtherInterface
также определяется в вашем проекте "Интерфейсы". Затем, пока ваш конкретный класс наследует как от IThing
, так и IOtherInterface
, вы можете использовать метод DoMyThing
и передать экземпляр вашего конкретного класса без циклической зависимости. Ваш код может стать:
public interface ICustomButton
{
void Animate<T>(T strategy) where T : IAnimateable;
}
В интерфейсе нет ссылки на конкретный класс AnimatorStrategy
.