Как отказаться от наследства

Я искал здесь и на других форумах и не нашел хорошего ответа. Я вроде как знаю, что расширение классов - не лучшая практика. И что я должен больше использовать интерфейсы. моя проблема в том, что обычно я начинаю создавать интерфейсы, а затем перехожу к абстрактным классам, потому что всегда есть некоторые функции, которые я хочу реализовать в суперклассе, чтобы мне не приходилось реплицировать их в каждом дочернем классе. Например, у меня есть класс Vehicle и дочерние классы Car и Bike. многие функциональные возможности, такие как Move() и Stop(), могут быть реализованы в классе Vehicle, так что лучше всего поддерживать чистоту архитектуры, избегать повторения кода и использовать интерфейсы вместо наследования? Большое спасибо!

(если вы понятия не имеете, почему я это спрашиваю, вы можете прочитать эту интересную статью: http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html)


person Paulo Ricca    schedule 27.05.2011    source источник
comment
То, что вы предлагаете, кажется вполне разумным. Интерфейс -> Аннотация -> и т.д.   -  person tofutim    schedule 27.05.2011


Ответы (8)


Согласитесь с тофутимом - в вашем текущем примере разумно двигаться и останавливаться на транспортном средстве.

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

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

Например, типы транспортных средств включают подводную лодку, лодку, самолет, автомобиль и велосипед. Вы можете разбить его на интерфейсы... IMoveable + Forward() + Backward() + Left() + Right()

IFloatable + Dock()

ISink() + BlowAir()

IFly() + взлет() + приземление()

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

Проблема в том, что вы можете в конечном итоге дублировать некоторый код в классе автомобиля/велосипеда для IMoveable.Left() и IMoveable.Right(). Вы можете превратить это во вспомогательный метод и агрегировать помощник... но если вы доведете его до логического завершения, вы все равно закончите рефакторингом многих вещей обратно в базовые классы.

Наследование и агрегация - это инструменты... ни один из них не является "злом".

Надеюсь, это поможет.

person Pete - MSFT    schedule 27.05.2011
comment
Эй, Питер, спасибо за ваш ответ. Итак, вы думаете, что эти интерфейсы для каждого поведения были бы полезными? Не могли бы вы сделать что-то вроде: класс Car расширяет LandVehicle, реализует IMoveable? - person Paulo Ricca; 27.05.2011
comment
классический ответ - зависит. Как правило, вы хотите быть осторожными с глубокими иерархиями наследования, но в то же время - если это действительно наземное транспортное средство, то, во что бы то ни стало, наследуйте от него. Если все наземные транспортные средства подвижны, то поместите интерфейс на них. - person Pete - MSFT; 27.05.2011

Наследование («расширение классов») накладывает существенные ограничения на дизайн классов, и я не уверен, что использование интерфейсов в качестве замены наследованию — лучшая идея, поскольку она не прошла DRY-тест.

В наши дни композиция предпочтительнее наследования, поэтому вы можете рассмотреть этот пост: Предпочесть композицию наследованию?

person Rob Raisch    schedule 27.05.2011
comment
Спасибо, Роб, я не знал, что композиция является формальным шаблоном, это довольно интересно, спасибо за ссылку! - person Paulo Ricca; 27.05.2011

Интересный вопрос. У всех разные подходы. Но это все основано на личном опыте и выборе.

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

Это дает несколько преимуществ, основанных на опыте.
1. Во время вызовов функций вы можете передавать элементы как тип интерфейса или тип абстрактного класса.
2. Общие переменные, такие как ID, имена и т. д., могут быть помещены в абстрактный класс.
3. Простота обслуживания. Например, если вы хотите внедрить новый интерфейс, просто быстро внедрите его в абстрактном виде.

person Jeyara    schedule 27.05.2011
comment
Это достаточно просто и охватывает множество случаев, мне нравится такой подход, спасибо! - person Paulo Ricca; 27.05.2011

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

Как правило, вы будете использовать абстрактный класс в своей конкретной структуре реализации, чтобы предоставить его потребителям некоторые базовые функции. Если вы просто заявляете, что никогда не используете абстрактный класс в пользу интерфейса - это просто неправильно с практической точки зрения. Что если вам нужно 10 реализаций одного и того же интерфейса с 90% одинаковым кодом. Копировать код 10 раз? Хорошо, может быть, вы использовали бы здесь абстрактный класс, но добавили бы интерфейс поверх него. Но зачем вам это делать, если вы никогда не собираетесь предлагать свою иерархию классов внешним потребителям? Я использую слово внешний в очень широком смысле - это может быть просто другой пакет в вашем проекте или удаленный потребитель.

В конечном счете, многие из этих вещей являются предпочтениями и личным опытом, но я не согласен с большинством общих утверждений, таких как extends is evil. Я также предпочитаю не использовать дополнительные классы (интерфейсы или абстрактные), если только это не требуется для определенных частей дизайна.

Просто мои два цента.

person Alex Gitelman    schedule 27.05.2011
comment
Спасибо за ваш ответ, Алекс, у вас есть хороший способ сделать концепции прозрачными, это действительно прояснило для меня. Согласен, статья слишком радикальная, наверное, чтобы привлечь внимание :) - person Paulo Ricca; 27.05.2011

Наследование позволяет повторно использовать код и замещать его, но ограничивает полиморфизм. Композиция допускает повторное использование кода, но не взаимозаменяемость. Интерфейсы допускают заменяемость, но не повторное использование кода.

Решение об использовании наследования, композиции или интерфейсов сводится к нескольким простым принципам:

  1. If one needs both code reuse and substitutability, and the restrictions imposed on polymorphism aren't too bad, use inheritance.

  2. If one needs code reuse, but not substitutability, use composition.

  3. If one needs substitutability, but not code reuse, or if the restrictions inheritance would impose upon polymorphism would be worse than duplicated code, use interfaces.

  4. If one needs substitutability and code reuse, but the restrictions imposed by polymorphism would be unacceptable, use interfaces to wrap encapsulated objects.

  5. If one needs substitutability and code reuse, and the restrictions imposed by polymorphism would not pose any immediate problem but might be problematic for future substitutable classes, derive a model base class which implements an interface, and have those classes that can derive from it do so. Avoid using variables and parameters of the class type, though--use the interface instead. If you do that, and there is a need for a substitutable class which cannot very well derive from the model base class, the new class can implement the interface without having to inherit from the base; if desired, it may implement the interface by wrapping an encapsulated instance of a derivative of the model type.

При принятии решения о том, могут ли будущие замещаемые классы иметь трудности с получением от базового класса, может потребоваться суждение. Я склонен думать, что подход № 5 часто предлагает лучшее из всех миров, когда требуется взаимозаменяемость. Часто это дешевле, чем использование только интерфейсов, и ненамного дороже, чем использование только наследования. Если есть потребность в будущих классах, которые можно заменять, но нельзя вывести из базы, может потребоваться преобразовать код для использования подхода № 5. Использование подхода № 5 с самого начала позволит избежать необходимости рефакторинга кода позже. (Конечно, если нет необходимости заменять класс, который не может быть производным от базового, дополнительные затраты, какими бы незначительными они ни были, могут оказаться ненужными).

person supercat    schedule 27.05.2011

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

person alexD    schedule 27.05.2011
comment
Спасибо, Алекс, я искал ответ на общую архитектуру, это был просто пример (слишком упрощенный для проблемы, я думаю..) - person Paulo Ricca; 27.05.2011

  • Не верьте всему, что вы читаете

    • Many times, inheritance is not bad, in fact, for data-hiding, it may be a good idea.
  • По сути, используйте политику «только интерфейсы» только тогда, когда вы создаете очень маленькое дерево классов, в противном случае, я обещаю, это будет больно. Предположим, у вас есть «класс» Person (имеет eat() и sleep) и два подкласса, Mathematician (имеет doProblem() ) и Engineer ( buildSomething() ), затем переходите к интерфейсам. Если вам нужно что-то вроде класса Car, а затем 56 миллионов типов автомобилей, используйте наследование.

ИМХО.

person Dhaivat Pandya    schedule 27.05.2011
comment
хм, хорошо, спасибо, так что в этом примере с Person вы бы написали интерфейс Person с eat() и sleep()? и где эти методы будут реализованы? как на математике, так и на инженере? - person Paulo Ricca; 27.05.2011
comment
Да, и математик, и инженер. - person Dhaivat Pandya; 27.05.2011
comment
но не будет ли это слишком лишним? - person Paulo Ricca; 27.05.2011

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

Но если сравнивать интерфейс с абстрактным классом, то абстрактный класс всегда больше, чем интерфейс. Интерфейс — это всегда какой-то аспект класса, некая точка зрения, а не весь класс.

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

person gaRex    schedule 27.05.2011