Организация интерфейсов - PullRequest
11 голосов
/ 16 июля 2009

Я просто читаю Agile Принципы, Шаблоны и Практики в C # Р. Мартина и М. Мартина, и они предлагают в своей книге сохранить все ваши интерфейсы в отдельном проекте, например. Интерфейсы .

Например, если у меня есть проект Gui , который содержит все мои пользовательские классы графического интерфейса, я сохраню их интерфейсы в проекте Интерфейсы . В частности, у меня был класс CustomButton в Gui , я бы оставил интерфейс ICustomButton в Interfaces .

Преимущество состоит в том, что любой класс, которому требуется ICustomButton, не нуждается в ссылке на сам Gui , а только на гораздо более легкий Интерфейс проект.

Кроме того, если класс в проекте Gui изменится и тем самым приведет к его перестройке, только проекты, непосредственно ссылающиеся на CustomButton, потребуют перекомпиляции, тогда как те, которые ссылаются на ICustomButton, могут остаться нетронутыми.

Я понимаю эту концепцию, но вижу проблему:

Допустим, у меня есть этот интерфейс:

public interface ICustomButton
{
    void Animate(AnimatorStrategy strategy);
}

Как вы можете видеть, это относится к AnimatorStrategy, который является конкретным классом и поэтому будет находиться в другом проекте, назовем его Animation . Теперь интерфейсный проект должен ссылаться на Animation . С другой стороны, если Animation использует интерфейс, определенный в Interfaces , он должен ссылаться на него.

Циклическая зависимость - «Вот и мы».

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

Я не знаю, хотел бы я решить эти накладные расходы в процессе разработки.

Есть предложения?

Ответы [ 4 ]

16 голосов
/ 16 июля 2009

Остерегайтесь всегда, всегда и никогда - особенно рядом со всеми, ни с кем, ни с каждым.

Вы должны всегда поместить всех ваших интерфейсов в отдельную сборку? Нет - не обязательно.

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

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

Что касается приведенного вами примера - я бы посоветовал вам НЕ ссылаться на классы в вашей системе из интерфейсов. Когда бы ни было возможно, я стараюсь, чтобы интерфейсы ссылались только на другие интерфейсы - это сохраняет связь между собой. Вы не всегда можете достичь этого - и поэтому, когда у вас есть эти типы интерфейсов - вы должны хранить их в соответствии со сборкой, от которой они зависят.

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

3 голосов
/ 16 июля 2009

Простой ответ: AnimatorStrategy также должен быть интерфейсом. Реализация для AnimatorStrategy находится в проекте Animation, но интерфейс будет в проекте AnimationInterfaces или Interfaces (при необходимости).

Не обязательно иметь один проект интерфейса, но по одному для каждой функциональной группы. Клиенты Animation будут ссылаться на AnimationInterfaces, а клиенты Gui будут ссылаться на GuiInterfaces.

Таким образом, вы сохраняете свои контракты, не смешивая их.

Для чего бы то ни было, я предпочитаю пойти другим путем с соглашениями об именах. Поместите интерфейсы в Animation, а реализации в AnimationImpl (или аналогичные). Таким образом, вы ссылаетесь на функциональное имя.

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

Я думаю, что циклические зависимости могут быть неизбежны, даже если вы объявляете все как интерфейсы, потому что в сборке Foo может быть метод, который принимает параметр IBar, и метод в сборке Bar, который принимает параметр IFoo.

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

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

Как насчет определения интерфейса с методами, которые принимают универсальные типы? Э.Г.

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.

...