Должен ли каждый объект иметь интерфейс и все объекты слабо связаны? - PullRequest
38 голосов
/ 09 декабря 2008

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

Правильно ли это и является ли это правило, которому всегда следует следовать?

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

Я использую C # и dot net 2.0, однако считаю, что этот вопрос подходит для многих языков.

Ответы [ 7 ]

52 голосов
/ 09 декабря 2008

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

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

13 голосов
/ 09 декабря 2008

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

Например: Если вы работаете над моделью предметной области, то эта модель не должна быть интерфейсом. Однако, если эта модель предметной области хочет вызывать классы обслуживания (например, доступ к данным, функции операционной системы и т. Д.), Вам следует искать интерфейсы для этих компонентов. Это уменьшает связь между классами и означает, что это интерфейс или «контракт», который связан.

В этой ситуации вы начинаете находить, что гораздо проще писать модульные тесты (потому что у вас могут быть заглушки / насмешки / подделки для доступа к базе данных и т. Д.) И можете использовать IoC для замены компонентов без перекомпиляции ваших приложений.

8 голосов
/ 09 декабря 2008

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

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

О, да, и, если вы серьезно занимаетесь интерфейсами, остерегайтесь веб-сервисов. Если вам нужно представить свои методы объекта через веб-сервис, они не могут реально возвращать или принимать типы интерфейса, а только конкретные типы (если вы не собираетесь писать вручную всю свою сериализацию / десериализацию). Да, это укусило меня большое время ...

5 голосов
/ 09 декабря 2008

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

В качестве примера, все потоковые объекты в .NET происходят от System.IO.Stream, который является абстрактным классом. Это позволяет Microsoft легко добавлять новые функции. Во второй версии frameworkj они добавили свойства ReadTimeout и WriteTimeout , не нарушая код. Если бы они использовали интерфейс (скажем, IStream), то они бы не смогли этого сделать. Вместо этого им пришлось бы создать новый интерфейс для определения методов тайм-аута, и мы должны были бы написать код, условно приведенный к этому интерфейсу, если бы мы хотели использовать функциональность.

3 голосов
/ 09 декабря 2008

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

Например, в моем приложении CAM у меня есть CuttingPath, связанный с набором точек. Нет смысла иметь интерфейс IPointList, так как CuttingPath всегда будут состоять из точек в моем приложении.

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

Наши приложения поддерживались с середины 80-х годов и перешли к объектно-ориентированному дизайну в конце 90-х годов. Я обнаружил, что то, что может измениться, значительно превзошло то, что я первоначально думал, и использование интерфейсов возросло. Например, когда-то наш DrawingPath состоял из точек. Но теперь он состоит из сущностей (сплайнов, дуг и т. Д.). Таким образом, он указывает на EntityList, который является коллекцией Object, реализующей интерфейс IEntity.

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

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

2 голосов
/ 06 марта 2012

Я пытался воспользоваться советом «код для интерфейса» буквально в недавнем проекте. Конечным результатом было по сути дублирование открытого интерфейса (small i) каждого класса точно один раз в реализации интерфейса (big I). Это довольно бессмысленно на практике.

Лучшая стратегия, которую я чувствую, - ограничить реализацию вашего интерфейса глаголами :

Print()
Draw()
Save()
Serialize()
Update()

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

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

1 голос
/ 09 декабря 2008

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

Допустим, у вас есть несколько типов классов клиентов, которые немного различаются, но имеют общие свойства. В этом случае замечательно иметь интерфейс ICustomer, чтобы связать их вместе, логично. Сделав это, вы можете создать CustomerHander класс / метод, который обрабатывает объекты ICustomer таким же образом, вместо того, чтобы создавать метод handerl для каждого варианта клиентов.

Это сила интерфейсов. Если у вас есть только один класс, который реализует интерфейс, то интерфейс не слишком помогает, он просто сидит и ничего не делает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...