Наследование и интерфейсы
Это несколько следующий вопрос к этому question.
Предположим, что у меня есть дерево наследования следующим образом:
Car -> Ford -> Mustang -> MustangGT
Есть ли преимущество в определении интерфейсов для каждого из этих классов? Пример:
ICar -> IFord -> IMustang -> IMustangGT
Я вижу, что, возможно, другие классы (например, Chevy
) хотели бы реализовать Icar
или IFord
и, возможно, даже IMustang
, но, вероятно, не IMustangGT
, потому что это так специфично. Являются ли интерфейсы лишними в этом случае?
Кроме того, я бы подумал, что любой класс, который хотел бы реализовать IFord
, определенно захотел бы использовать свое одно наследование, наследуя от Ford
, чтобы не дублировать код. Если это задано, в чем преимущество реализации IFord
?
Ответы
Ответ 1
По моему опыту, интерфейсы лучше всего использовать, когда у вас есть несколько классов, каждый из которых должен отвечать на один и тот же метод или методы, чтобы их можно было использовать взаимозаменяемо другим кодом, который будет написан против общего интерфейса этих классов. Наилучшее использование интерфейса - это когда протокол важен, но основная логика может быть разной для каждого класса. Если вы в противном случае были бы дублирующей логикой, вместо этого рассмотрите абстрактные классы или наследование стандартного класса.
И в ответ на первую часть вашего вопроса я бы рекомендовал не создавать интерфейс для каждого из ваших классов. Это излишне загромождает вашу структуру классов. Если вы обнаружите, что вам нужен интерфейс, вы всегда можете добавить его позже. Надеюсь, это поможет!
Адам
Ответ 2
Я также согласен с ответом adamalex, что интерфейсы должны делиться классами, которые должны отвечать определенным методам.
Если классы имеют сходную функциональность, но не имеют прямого отношения друг к другу в отношении предков, тогда интерфейс будет хорошим способом добавить эту функцию к классам без дублирования функций между ними. (Или иметь несколько реализаций с небольшими различиями.)
Пока мы используем аналогию с автомобилем, конкретный пример. Пусть говорят, что мы имеем следующие классы:
Car -> Ford -> Escape -> EscapeHybrid
Car -> Toyota -> Corolla -> CorollaHybrid
Автомобили имеют wheels
и могут Drive()
и Steer()
. Поэтому эти методы должны существовать в классе Car
. (Вероятно, класс Car
будет абстрактным классом.)
Спустившись по линии, мы получим различие между Ford
и Toyota
(вероятно, реализовано как разница в типе эмблемы на автомобиле, опять же, вероятно, абстрактный класс.)
Затем, наконец, мы имеем класс Escape
и Corolla
, которые являются классами, которые полностью реализованы как автомобиль.
Теперь, как мы можем сделать гибридный автомобиль?
У нас может быть подкласс Escape
, который EscapeHybrid
, который добавляет метод FordsHybridDrive()
и подкласс Corolla
, который CorollaHybrid
с ToyotasHybridDrive()
. Методы в основном делают то же самое, но все же у нас разные методы. Yuck. Похоже, мы можем сделать лучше, чем это.
Скажем, что гибрид имеет метод HybridDrive()
. Поскольку мы не хотим, чтобы у меня было два разных типа гибридов (в идеальном мире), мы можем создать интерфейс IHybrid
, который имеет метод HybridDrive()
.
Итак, , если мы хотим создать класс EscapeHybrid
или CorollaHybrid
, все, что нам нужно сделать, это реализовать интерфейс IHybrid
.
Для примера в реальном мире давайте взглянем на Java. Класс, который может выполнять сравнение объекта с другим объектом, реализует интерфейс Comparable
. Как следует из названия, интерфейс должен быть для сопоставимого класса, отсюда и название "Comparable".
Как интересный вопрос, автомобиль пример используется в Интерфейсы урок Учебник по Java.
Ответ 3
Вы не должны реализовывать какой-либо из этих интерфейсов вообще.
Наследование классов описывает то, что объект (например: его идентификатор). Это прекрасно, однако большую часть времени, что является объектом, гораздо менее важно, чем то, что делает объект. Здесь присутствуют интерфейсы.
Интерфейс должен описывать то, что делает объект), или то, что он действует. Под этим я подразумеваю это поведение и набор операций, которые имеют смысл с учетом этого поведения.
Таким образом, хорошие имена интерфейсов обычно должны иметь форму IDriveable
, IHasWheels
и т.д. Иногда лучший способ описать это поведение - ссылаться на хорошо известный другой объект, поэтому вы можете сказать "действует как один из них" (например: IList
), но IMHO, что форма именования находится в меньшинстве.
Учитывая эту логику, сценарии, в которых смысл наследования интерфейса имеет смысл, полностью и полностью отличаются от сценариев, где имеет место наследование объектов - часто эти сценарии вообще не относятся к каждой стороне.
Надеюсь, что вы сможете продумать интерфейсы, которые вам действительно нужны: -)
Ответ 4
Я бы сказал, что только создаю интерфейс для вещей, о которых вам нужно обратиться. У вас могут быть другие классы или функции, которые необходимо знать о машине, но как часто будет что-то, что нужно знать о брод?
Ответ 5
Не создавайте вещи, которые вам не нужны. Если окажется, что вам нужны интерфейсы, это небольшое усилие, чтобы вернуться и построить их.
Кроме того, на педантичной стороне, надеюсь, вы на самом деле не строите что-то похожее на эту иерархию. Это не то, на что нужно использовать наследование.
Ответ 6
Создайте его только после того, как необходимый уровень функциональности станет необходимым.
Код повторного факторинга всегда находится в процессе продолжения.
Доступны инструменты, которые позволят вам извлечь, если необходимо, интерфейс.
НАПРИМЕР. http://geekswithblogs.net/JaySmith/archive/2008/02/27/refactor-visual-studio-extract-interface.aspx
Ответ 7
Сделайте ICar и все остальное (Make = Ford, Model = Mustang и т.д.) как члены класса, который реализует интерфейс.
Возможно, вы захотите, чтобы ваш класс Ford и, например, GM-класс, и оба использовали ICar, чтобы использовать полиморфизм, если вы не хотите идти по пути проверки Make == Whatever
, что до вашего стиля.
В любом случае - на мой взгляд, это атрибуты автомобиля, а не наоборот - вам нужен только один интерфейс, потому что методы распространены: Brake, SpeedUp и т.д.
Может ли Ford делать то, что другие автомобили не могут? Я так не думаю.
Ответ 8
Я создаю первые два уровня: ICar и IFord и оставлю второй уровень один, пока мне не понадобится интерфейс на этом втором уровне.
Ответ 9
Подумайте о том, как ваши объекты должны взаимодействовать друг с другом в вашей проблемной области, и подумайте, нужно ли вам иметь более одной реализации конкретной абстрактной концепции. Используйте интерфейсы, чтобы обеспечить контракт вокруг концепции, с которой взаимодействуют другие объекты.
В вашем примере я бы предположил, что Ford, вероятно, является производителем, а Mustang - это ModelName. Значение, используемое производителем Ford, поэтому у вас может быть что-то большее:
IVehichle → CarImpl, MotorbikeImpl - имеет -Производитель имеет-многие ModelNames
Ответ 10
В этом ответе о различии между интерфейсом и классом я объяснил, что:
-
Интерфейс
- показывает, что такое понятие (в терминах "что есть", во время компиляции) и используется для значений (MyInterface x =...)
Класс
- предоставляет то, что делает концепция (фактически выполняется во время выполнения), и используется для значений или для объектов (MyClass x или aMyClass.method())
Итак, если вам нужно сохранить в "Форде" переменную (понятие "значение" ) разных подклассов Форда, создайте IFord. В противном случае, не беспокойтесь, пока вам это не понадобится.
Это один из критериев: если он не соблюден, IFord, вероятно, бесполезен.
Если это выполняется, тогда применяются другие критерии, представленные в предыдущих ответах: Если у Форда более богатый API, чем у Автомобиля, IFord полезен для целей полиморфизмов. Если нет, ICar достаточно.
Ответ 11
По моему мнению, интерфейсы - это инструмент для обеспечения требования, чтобы класс реализовал определенную подпись или (как мне нравится думать об этом) определенное "поведение". Мне кажется, что если "Капитал" в начале моего onterface имена как личное местоимение, и я пытаюсь назвать свои интерфейсы, чтобы их можно было читать таким образом... ICanFly, IKnowHowToPersistMyself IAmDisplayable и т.д. Итак, в вашем примере я бы не создал интерфейс для зеркалирования полной публичной подписи любого конкретного класса. Я бы проанализировал публичную подпись (поведение), а затем разделил членов на более мелкие логические группы (чем меньше, тем лучше), например (используя ваш пример) IMove, IUseFuel, ICarryPassengers, ISteerable, IAccelerate, IDepreciate и т.д.... Затем примените эти интерфейсы для любых других классов в моей системе нуждаются в них
Ответ 12
В общем, лучший способ подумать об этом (и много вопросов в OO) - это подумать о понятии контракта.
Контракт определяется как соглашение между двумя (или более) сторонами, в котором указаны конкретные обязательства, которые должна выполнить каждая из сторон; в программе это то, что предоставит класс, и что вы должны предоставить классу для получения услуг. Интерфейс устанавливает контракт, который должен удовлетворять любой класс, реализующий интерфейс.
Учитывая это, ваш вопрос несколько зависит от того, какой язык вы используете и что хотите делать.
После многих лет работы с OO (например, о мой бог, 30 лет) я обычно писал интерфейс для каждого контракта, особенно в Java, потому что он делает тесты намного проще: если у меня есть интерфейс для класса, Я могу легко строить макеты, почти тривиально.
Ответ 13
Интерфейсы предназначены для общего публичного API, и пользователи будут ограничены использованием этого открытого API. Если вы не собираетесь использовать методы типа IMustangGT
для типов, вы можете ограничить иерархию интерфейса ICar
и IExpensiveCar
.
Ответ 14
Наследовать только интерфейсы и абстрактные классы.
Если у вас есть несколько классов, которые почти одинаковы, вам необходимо реализовать большинство методов, использования и интерфейса в сочетании с покупкой другого объекта.
Если классы 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();
}