Наследование и интерфейсы

Это своего рода дополнительный вопрос к этому вопрос.

Предположим, у меня есть дерево наследования следующим образом:

Car -> Ford -> Mustang -> MustangGT

Есть ли польза от определения интерфейсов для каждого из этих классов? Пример:

ICar -> IFord -> IMustang -> IMustangGT

Я вижу, что, возможно, другие классы (например, Chevy) захотят реализовать Icar или IFord и, возможно, даже IMustang, но, вероятно, не IMustangGT, потому что это очень специфично. Интерфейсы в этом случае лишние?

Кроме того, я думаю, что любой класс, который хотел бы реализовать IFord, определенно хотел бы использовать его одно наследование, наследуя от Ford, чтобы не дублировать код. Если это данность, то какая польза от реализации IFord?


person Rob Sobers    schedule 06.11.2008    source источник


Ответы (14)


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

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

Адам

person Adam Alexander    schedule 06.11.2008
comment
Если вы обнаружите, что вам нужен интерфейс, вы всегда можете добавить его позже. Здесь проявляется принцип ЯГНИ. Отложите все такие решения до того времени, когда это потребуется. - person Hemanshu Bhojak; 10.03.2009

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

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

Пока мы используем аналогию с автомобилем, конкретный пример. Допустим, у нас есть следующие классы:

Car -> Ford   -> Escape  -> EscapeHybrid
Car -> Toyota -> Corolla -> CorollaHybrid

Автомобили имеют wheels и могут Drive() и Steer(). Таким образом, эти методы должны существовать в классе Car. (Вероятно, класс Car будет абстрактным классом.)

Идя вниз по строке, мы получаем различие между Ford и Toyota (вероятно, реализованное как разница в типе эмблемы на автомобиле, опять же, вероятно, абстрактный класс).

Затем, наконец, у нас есть классы Escape и Corolla, полностью реализованные в виде автомобиля.

Как мы можем создать гибридный автомобиль?

У нас может быть подкласс Escape, то есть EscapeHybrid, который добавляет метод FordsHybridDrive(), и подкласс Corolla, то есть CorollaHybrid, с методом ToyotasHybridDrive(). Методы в основном делают одно и то же, но у нас разные методы. Фу! Похоже, мы можем добиться большего.

Допустим, у гибрида есть метод HybridDrive(). Поскольку мы не хотим иметь два разных типа гибридов (в идеальном мире), мы можем создать интерфейс IHybrid с методом HybridDrive().

Итак, если мы хотим создать класс EscapeHybrid или CorollaHybrid, все, что нам нужно сделать, это реализовать интерфейс IHybrid.

В качестве примера из реального мира давайте взглянем на Java. Класс, который может сравнивать объект с другим объектом, реализует интерфейс Comparable. Как следует из названия, интерфейс должен быть для класса, который сопоставим, отсюда и название "Сопоставимо".

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

person coobird    schedule 06.11.2008

Вы вообще не должны реализовывать какие-либо из этих интерфейсов.

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

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

Таким образом, хорошие имена интерфейсов обычно должны иметь форму IDriveable, IHasWheels и т. д. Иногда лучший способ описать такое поведение — сослаться на хорошо известный другой объект, поэтому вы можете сказать «действует как один из этих» (например: IList), но ИМХО такая форма именования находится в меньшинстве.

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

Надеюсь, это поможет вам продумать интерфейсы, которые вам действительно нужны :-)

person Orion Edwards    schedule 22.01.2009

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

person Emile    schedule 06.11.2008

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

Кроме того, с педантичной стороны, я надеюсь, что вы на самом деле не строите что-то похожее на эту иерархию. Это не то, для чего следует использовать наследование.

person Rik    schedule 06.11.2008

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

Рефакторинг кода всегда в процессе.

Доступны инструменты, которые позволят вам извлекать в интерфейс, если это необходимо. НАПРИМЕР. http://geekswithblogs.net/JaySmith/archive/2008/02/27/refactor-visual-studio-extract-interface.aspx

person Brian Schmitt    schedule 06.11.2008

Сделайте ICar и все остальное (Make=Ford, Model=Mustang и прочее) как члены класса, реализующего интерфейс.

Возможно, вы захотите иметь свой класс Ford и, например, класс GM, и оба реализуют ICar, чтобы использовать полиморфизм, если вы не хотите идти по пути проверки Make == Whatever, это зависит от вашего стиля.

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

Может ли Ford делать то, чего не могут другие автомобили? Я так не думаю.

person JohnIdol    schedule 06.11.2008

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

person Jean    schedule 06.11.2008

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

В вашем примере я бы предположил, что Ford, вероятно, является производителем, а Mustang — это значение ModelName, используемое производителем Ford, поэтому у вас может быть что-то вроде:

IVehichle -> CarImpl, MotorbikeImpl - у производителя есть много названий моделей

person Community    schedule 06.11.2008

В этом ответе о разнице между интерфейсом и классом я объяснил, что:

  • Интерфейс показывает, что такое концепция является (с точки зрения «что является» действительным во время компиляции) и используется для значений (МойИнтерфейс х = ...)
  • класс показывает, что концепция делает (фактически выполняется во время выполнения) и используется для значений или объектов (MyClass x или aMyClass.method() )

Поэтому, если вам нужно сохранить в переменной «Форд» (понятие «значение») различные подклассы Форда, создайте IFord. В противном случае не беспокойтесь, пока вам это действительно не понадобится.

Это один из критериев: если он не соблюдается, IFord, вероятно, бесполезен.
Если он соблюдается, применяются другие критерии, изложенные в предыдущих ответах: если у Ford более богатый API, чем у автомобиля, iFord полезен. для целей полиморфизма. Если нет, ICar достаточно.

person VonC    schedule 06.11.2008

На мой взгляд, интерфейсы — это инструмент для обеспечения выполнения требования о том, чтобы класс реализовывал определенную сигнатуру или (как мне нравится думать об этом) определенное «поведение». Я думаю, что если Capital I в начале моего имени интерфейса как личное местоимение, и я стараюсь называть свои интерфейсы так, чтобы их можно было прочитать таким образом... ICanFly, IKnowHowToPersistMyself, IAmDisplayable и т. д. учебный класс. Я бы проанализировал общедоступную подпись (поведение), а затем разделил бы участников на более мелкие логические группы (чем меньше, тем лучше), например (используя ваш пример) IMove, IUseFuel, ICarryPassengers, ISteerable, IAccelerate, IDepreciate и т. д.... А затем примените эти интерфейсы для любых других классов в моей системе, в которых они нуждаются

person Charles Bretana    schedule 06.11.2008

В общем, лучший способ подумать об этом (и о многих вопросах ООП) — подумать о понятии контракта.

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

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

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

person Charlie Martin    schedule 06.11.2008

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

person yfeldblum    schedule 06.11.2008

Наследовать только от интерфейсов и абстрактных классов.

Если у вас есть пара почти одинаковых классов, и вам нужно реализовать большинство методов, используйте и интерфейс в сочетании с покупкой другого объекта.
Если классы Мустанга такие разные то создадим не только интерфейс ICar, но и IMustang.
Так класс Форд и Мустанг могут наследоваться от ICar, а Мустанг и МустангGT от ICar и IMustang .

Если вы реализуете класс Ford и метод такой же, как у Mustang, покупайте у Mustang:

class Ford{
  public function Foo(){
    ...
    Mustang mustang  = new Mustang();
    return mustang.Foo();
}
person eddy147    schedule 06.11.2008