Должен ли каждый отдельный объект иметь интерфейс, а все объекты слабо связаны?

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

Это правильно и всегда ли это правило следует соблюдать?

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

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

По праву, вы должны. Слева у вас должен быть генератор, который сделает за вас "шаблон".

Pacerier 06.03.2012 09:21
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Принцип подстановки Лискова
Принцип подстановки Лискова
Принцип подстановки Лискова (LSP) - это принцип объектно-ориентированного программирования, который гласит, что объекты суперкласса должны иметь...
39
1
6 757
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В этом сильная сторона интерфейсов. Если у вас есть только один класс, реализующий интерфейс, то интерфейс не очень помогает, он просто сидит и ничего не делает.

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

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

Наследование интерфейсов разрешено. Разве это не привело бы к тому же результату?

Tom W 06.03.2012 12:11

Ты очень хорошо подмечаешь, Шон, но у меня тот же вопрос, что и у Тома. Что бы произошло, если бы они опубликовали IStream2 : IStream? Большой беспорядок? Вы можете уточнить?

toddmo 15.03.2015 02:06

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

Я считаю, что лучшая стратегия - ограничить реализацию интерфейса глаголы:

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

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

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

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

Frederik Krautwald 06.08.2014 14:20

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

Tom W 06.08.2014 14:37

Мне жаль. Я неправильно понял: «ограничьте реализацию интерфейса глаголами».

Frederik Krautwald 06.08.2014 19:22

Я думаю, что больше людей должны любить вас и публиковать истории о том, что пошло не так, потому что новичкам вроде меня довольно сложно «заглянуть вперед» в последствия того, что произойдет, если мы отдадим предпочтение x вместо y. Спасибо.

toddmo 15.03.2015 02:16

Другие вопросы по теме