Это своего рода вопрос, продолжающий этот вопрос.
Предположим, у меня есть следующее дерево наследования:
Car -> Ford -> Mustang -> MustangGT
Есть ли польза от определения интерфейсов для каждый этих классов? Пример:
ICar -> IFord -> IMustang -> IMustangGT
Я вижу, что, возможно, другие классы (например, Chevy) захотят реализовать Icar или IFord и, возможно, даже IMustang, но, вероятно, не IMustangGT, потому что он настолько специфичен. Интерфейсы в этом случае лишние?
Кроме того, я бы подумал, что любой класс, который захочет реализовать IFord, определенно захочет использовать свое единственное наследование, унаследовав от Ford, чтобы не дублировать код. Если это дано, в чем польза от внедрения IFord?


Я бы создал первые два уровня, ICar и IFord, и оставил бы второй уровень в покое, пока мне не понадобится интерфейс на этом втором уровне.
По моему опыту, интерфейсы лучше всего использовать, когда у вас есть несколько классов, каждый из которых должен реагировать на один и тот же метод или методы, чтобы их можно было взаимозаменяемо использовать другим кодом, который будет написан для общего интерфейса этих классов. Лучше всего использовать интерфейс, когда важен протокол, но лежащая в основе логика может быть разной для каждого класса. Если в противном случае вы будете дублировать логику, рассмотрите вместо этого абстрактные классы или наследование стандартных классов.
И в ответ на первую часть вашего вопроса я бы рекомендовал не создавать интерфейс для каждого из ваших классов. Это излишне загромождает структуру вашего класса. Если вы обнаружите, что вам нужен интерфейс, вы всегда можете добавить его позже. Надеюсь это поможет!
Адам
Я бы сказал, создавайте интерфейс только для тех вещей, на которые вам нужно сослаться. У вас могут быть другие классы или функции, которым нужно знать об автомобиле, но как часто нужно будет что-то знать о броде?
Не создавайте ненужных вещей. Если окажется, что интерфейсы вам понадобятся, нужно будет вернуться и создать их без особых усилий.
Кроме того, что касается педантизма, я надеюсь, что вы на самом деле не строите что-то похожее на эту иерархию. Это не то, для чего следует использовать наследование.
Создавайте его только тогда, когда такой уровень функциональности становится необходим.
Код рефакторинга - это постоянно действующий процесс.
Доступны инструменты, позволяющие при необходимости извлекать файлы из интерфейса. НАПРИМЕР. http://geekswithblogs.net/JaySmith/archive/2008/02/27/refactor-visual-studio-extract-interface.aspx
Наследовать только от интерфейсов и абстрактных классов.
Если у вас есть пара классов, которые почти одинаковы, и вам нужно реализовать большинство методов, используйте интерфейс в сочетании с покупкой другого объекта.
Если классы 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();
}
Сделайте ICar и все остальные (Make = Ford, Model = Mustang и т. д.) Членами класса, реализующего интерфейс.
Вы можете захотеть иметь свой класс Ford и, например, класс GM, и оба реализуют ICar для использования полиморфизма, если вы не хотите идти по маршруту проверки Make == Whatever, это зависит от вашего стиля.
В любом случае - на мой взгляд, это атрибуты автомобиля, а не наоборот - вам просто нужен один интерфейс, потому что методы общие: Brake, SpeedUp и т. д.
Может ли Ford делать то, чего не могут другие автомобили? Я так не думаю.
Тщательно подумайте о том, как ваши объекты должны взаимодействовать друг с другом в вашей проблемной области, и подумайте, нужно ли вам иметь более одной реализации конкретной абстрактной концепции. Используйте интерфейсы, чтобы предоставить контракт вокруг концепции, с которой взаимодействуют другие объекты.
В вашем примере я бы предположил, что Ford, вероятно, является производителем, а Mustang - значением ModelName, используемым производителем Ford, поэтому у вас может быть что-то вроде:
IVehichle -> CarImpl, MotorbikeImpl - у производителя есть много названий моделей
В этом ответе о разница между интерфейсом и классом я объяснил, что:
Поэтому, если вам нужно сохранить в переменной Ford (понятие «значение») различные подклассы Ford, создайте IFord. В противном случае не беспокойтесь, пока это действительно не понадобится.
Это один из критериев: если он не выполняется, IFord, вероятно, бесполезен. Если он соблюдается, то применяются другие критерии, представленные в предыдущих ответах: если у Ford более богатый API, чем у автомобиля, IFord полезен для целей полиморфизма. Если нет, достаточно ICar.
Я также согласен с ответ адамалекса, что интерфейсы должны совместно использоваться классами, которые должны реагировать на определенные методы.
Если классы имеют схожую функциональность, но не связаны напрямую друг с другом в наследственных отношениях, то интерфейс будет хорошим способом добавить эту функцию к классам без дублирования функциональности между ними. (Или иметь несколько реализаций с небольшими различиями.)
Пока мы используем аналогию с автомобилем, конкретный пример. Допустим, у нас есть следующие классы:
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.
На мой взгляд, интерфейсы - это инструмент для обеспечения выполнения требования, чтобы класс реализовал определенную сигнатуру или (как мне нравится думать об этом) определенное «поведение». Для меня я думаю, что если Capital I в начале моих имен интерфейсов как личное местоимение, и я пытаюсь назвать свои интерфейсы так, чтобы их можно было читать таким образом ... ICanFly, IKnowHowToPersistMyself IAmDisplayable и т. д. Итак, в вашем примере я бы не стал создавать интерфейс для зеркалирования полной публичной подписи какой-либо конкретной класс. Я бы проанализировал публичную подпись (поведение), а затем разделил бы участников на более мелкие логические группы (чем меньше, тем лучше), например (используя ваш пример) IMove, IUseFuel, ICarryPassengers, ISteerable, IAccelerate, IDepreciate и т. д. И затем примените эти интерфейсы к любым другим классам в моей системе, в которых они нуждаются
В общем, лучший способ подумать об этом (и о многих вопросах в объектно-ориентированном проекте) - это подумать о понятии контракта.
Контракт определяется как соглашение между двумя (или более) сторонами, в котором указываются конкретные обязательства, которые каждая сторона должна выполнять; в программе это то, какие услуги будет предоставлять класс, и что вы должны предоставить классу, чтобы получить эти услуги. Интерфейс устанавливает контракт, которому должен удовлетворять любой класс, реализующий интерфейс.
Однако, имея это в виду, ваш вопрос в некоторой степени зависит от того, какой язык вы используете и что хотите делать.
После многих лет выполнения объектно-ориентированного проектирования (например, о боже, 30 лет) я обычно писал интерфейс для каждого контракта, особенно на Java, потому что это значительно упрощает тестирование: если у меня есть интерфейс для класса, я могу построить имитировать объекты легко, почти тривиально.
Интерфейсы предназначены для использования в качестве общего общедоступного API, и пользователи будут ограничены в использовании этого общедоступного API. Если вы не собираетесь использовать методы, зависящие от типа IMustangGT, вы можете ограничить иерархию интерфейсов до ICar и IExpensiveCar.
Вам вообще не следует реализовывать ни один из этих интерфейсов.
Наследование классов описывает что за объект является (например: его идентичность). Это нормально, однако в большинстве случаев объект является гораздо менее важен, чем объект делает. Вот тут-то и пригодятся интерфейсы.
Интерфейс должен описывать что за объект делает) или то, как он действует. Под этим я подразумеваю его поведение и набор операций, которые имеют смысл при таком поведении.
Таким образом, хорошие имена интерфейсов обычно должны иметь форму IDriveable, IHasWheels и т. д. Иногда лучший способ описать это поведение - сослаться на другой хорошо известный объект, поэтому вы можете сказать «действует как один из них» (например: IList), но ИМХО, эта форма именования находится в меньшинстве.
Учитывая эту логику, сценарии, в которых имеет смысл наследование интерфейса, полностью и полностью отличаются от сценариев, в которых имеет смысл наследование объекта - часто эти сценарии вообще не связаны друг с другом.
Надеюсь, это поможет вам продумать интерфейсы, которые вам действительно понадобятся :-)
«Если вы обнаружите, что вам нужен интерфейс, вы всегда можете добавить его позже». Здесь проявляется принцип ЯГНИ. Отложите принятие всех таких решений до необходимого времени.