Наследование и интерфейсы - PullRequest
2 голосов
/ 06 ноября 2008

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

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

Car -> Ford -> Mustang -> MustangGT

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

ICar -> IFord -> IMustang -> IMustangGT

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

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

Ответы [ 14 ]

16 голосов
/ 06 ноября 2008

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

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

Адам

6 голосов
/ 22 января 2009

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

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

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

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

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

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

6 голосов
/ 06 ноября 2008

Я также согласен с ответом 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".

Для интереса пример автомобиля используется в уроке Interfaces Java Tutorial .

2 голосов
/ 06 ноября 2008

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

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

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

2 голосов
/ 06 ноября 2008

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

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

2 голосов
/ 06 ноября 2008

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

1 голос
/ 06 ноября 2008

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

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

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

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

0 голосов
/ 06 ноября 2008

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

0 голосов
/ 06 ноября 2008

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

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

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

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

0 голосов
/ 06 ноября 2008

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

...