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

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

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

Car -> Ford -> Mustang -> MustangGT

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

ICar -> IFord -> IMustang -> IMustangGT

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

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

14 ответов

Решение

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

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

Адам

Я также согласен с ответом 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 интерфейс. Как следует из названия, интерфейс должен быть для класса, который сопоставим, отсюда и название "Comparable".

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

IVehichle -> CarImpl, MotorbikeImpl - имеет-производитель имеет-много ModelNames

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

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

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

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

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

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

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

class Ford{
  public function Foo(){
    ...
    Mustang mustang  = new Mustang();
    return mustang.Foo();
}
Другие вопросы по тегам