Интерфейсы - это перебор? - PullRequest
       19

Интерфейсы - это перебор?

0 голосов
/ 07 октября 2009

Это довольно простой вопрос ОО, но мне любопытно, если бы я пошел за борт.

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

IVehicle
  - IFlyable
      - IPlane
          - Plane
      - IHelicopter
          - Helicopter
  - IDrivable
      - ICar
          - Car
      - ITruck
          - Truck

Необходимы ли интерфейсы IPlane, IHelicopter, ICar и ITruck или классы должны напрямую реализовываться из IFlyable и IDriveable? Я ищу ответ "лучшие практики".

Ответы [ 7 ]

11 голосов
/ 07 октября 2009

Всякий раз, когда я вижу «абстракцию одного примера», я подозреваю - действительно ли этот уровень абстракции дает вам дополнительную ценность? Иногда это равно , обратите внимание - например, это облегчает тестирование через Mocks (если используется в сочетании с внедрением зависимостей - например, даже если единственным «реальным» классом, реализующим ICar, является класс Car, вы можете по-прежнему есть реализация MockCar, которая внедряется в клиенты ICar для более быстрой и тщательной проверки).

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

1 голос
/ 07 октября 2009

Уровень наследования всегда основан на требовании или уровне абстракции, который вы требовали для идеального решения требования.

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

0 голосов
/ 07 октября 2009

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

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

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

0 голосов
/ 07 октября 2009

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

Первый - всегда использовать интерфейс при объявлении аргумента или переменной. Вы решаете это выше, так как вам никогда не придется объявлять переменную Plane. Конечно, это верно только в том случае, если ваш класс самолетов не продлевает контракт, предоставленный IPlane, что будет серьезным «Хммм!»

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

для каждого автомобиля в транспортных средствах v.someMethodDefinedByIVehicle ()

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

0 голосов
/ 07 октября 2009

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

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

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

0 голосов
/ 07 октября 2009

Это зависит на самом деле. Если вам нужно различие между самолетом и вертолетом, то вам все равно может понадобиться IPlane, но если у вас нет или у вас есть только один самолет, то это может быть «излишним».

Например, вы можете даже исключить IFlyable:

interface IDrivable : IUsableItem

interface IPlane : IDrivable
class Cessna : IPlane
class Boeing : IPlane

interface IVehicle : IDrivable
class Semitrailer : IVehicle
class MorrisMinor : IVehicle
0 голосов
/ 07 октября 2009

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

Если вы планируете несколько реализаций интерфейса, сделайте это так, как вы показали. если у вас есть только одна реализация, покиньте интерфейс, пока он вам не понадобится.

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