Организация интерфейсов

Я просто читаю 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.