Подтвердить что ты не робот

Возможно ли ООП и полностью избежать наследования реализации?

Я выберу Java в качестве примера, большинство людей это знают, хотя все остальные языки OO также работают.

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

Однако мой класс также может "наследовать" (а не на языке Java) только интерфейс без реализации. На самом деле интерфейсы на самом деле названы таким образом в Java, они обеспечивают наследование интерфейса, но не наследуют какую-либо реализацию, поскольку все методы интерфейса не имеют реализации.

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

Что меня беспокоит, это наследование реализации означает повторное использование кода, одно из важнейших свойств языков OO. Теперь, если у Java не было классов (например, Джеймс Гослинг, крестный отец Java пожелал в соответствии с этой статьей), он решает все проблемы наследования реализации, но как бы вы могли сделать повторное использование кода тогда?

например. если у меня есть класс Car и Car, у вас есть метод move(), который заставляет автомобиль двигаться. Теперь я могу подклассифицировать Car для разных типов автомобилей, это все автомобили, но все это специализированные версии автомобилей. Некоторые могут двигаться по-другому, они должны перезаписать move() в любом случае, но большинство из них просто сохранит унаследованный ход, поскольку они перемещаются одинаково, как абстрактный родительский автомобиль. Предположим на секунду, что в Java есть только интерфейсы, только интерфейсы могут наследовать друг от друга, класс может реализовывать интерфейсы, но все классы всегда являются окончательными, поэтому ни один класс не может наследовать ни от какого другого класса.

Как бы вы избежали этого, когда у вас есть классы интерфейса Car и сотни автомобилей, вам нужно реализовать одинаковый метод move() для каждого из них? Какие понятия для повторного использования кода, кроме наследования реализации, существуют в мире ОО?

В некоторых языках есть Mixins. Является ли Mixins ответом на мой вопрос? Я читал о них, но я не могу представить, как Mixins будет работать в Java-мире, и если они действительно могут решить проблему здесь.

Другая идея заключалась в том, что существует класс, который реализует интерфейс Car, позволяет называть его AbstractCar и реализует метод move(). Теперь другие автомобили реализуют интерфейс Car, а внутри они создают экземпляр AbstractCar, и они реализуют свой собственный метод move(), вызывая move() на своем внутреннем абстрактном Car. Но разве это не будет напрасно тратить ресурсы (метод, вызывающий только другой метод - хорошо, JIT может встроить код, но все же) и используя дополнительную память для хранения внутренних объектов, вам даже не понадобится с наследованием реализации? (ведь каждому объекту требуется больше памяти, чем просто сумма инкапсулированных данных). И не сложно ли программисту писать фиктивные методы, например

public void move() {
    abstractCarObject.move();
}

?

Любой может себе представить, как избежать наследования реализации и все еще иметь возможность повторно использовать код в простой форме?

4b9b3361

Ответ 1

Короткий ответ: Да, возможно. Но вы должны делать это нарочно и не случайно (используя окончательный, абстрактный и дизайн с наследованием в виду и т.д.)

Длинный ответ:

Ну, наследование не на самом деле для "повторного использования кода", это для класса "специализация", я думаю, что это неверное истолкование.

Например, это очень плохая идея создать стек из вектора только потому, что они похожи. Или свойства из HashTable только потому, что они хранят значения. См. [Эффект].

"Повторное использование кода" было скорее "бизнес-представлением" характеристик OO, что означает, что объекты были легко распределены между узлами; и были переносимыми и не имели проблем с созданием предыдущих языков программирования. Это было доказано наполовину. Теперь у нас есть библиотеки, которые можно легко распределить; например, в java файлы jar могут использоваться в любом проекте, сохраняя тысячи часов разработки. У OO все еще есть некоторые проблемы с переносимостью и тому подобными, вот почему WebServices настолько популярны (как раньше это была CORBA), а вот другой поток.

Это один из аспектов "повторного использования кода". Другое - это то, что связано с программированием. Но в данном случае это не просто "сохранить" строки кода и создать хрупкие монстры, но конструировать с учетом наследования. Это пункт 17 в упомянутой ранее книге; Пункт 17: Дизайн и документ для наследования или запрет на него. См. [Эффективно]

Конечно, у вас может быть класс автомобилей и тонны подклассов. И да, подход, который вы упоминаете о интерфейсе автомобиля, AbstractCar и CarImplementation, - правильный путь.

Вы определяете "контракт", который должен придерживаться Автомобиль, и говорите, что это методы, которые я ожидал бы при разговоре о машинах. Абстрактный автомобиль, который имеет базовую функциональность, что каждый автомобиль, но оставляя и документируя методы, которые подклассы несут ответственность. В java вы делаете это, отмечая метод как абстрактный.

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

Наследование больше "специализируется" на классах, таким же образом "Грузовик" является специализированной версией автомобиля, а MosterTruck - специализированной версией Truck.

Это не делает sanse для создания подкласса "ComputerMouse" из Car только потому, что у него есть колесо (колесо прокрутки), как автомобиль, оно перемещается и имеет колесо внизу, чтобы сохранить строки кода. Он принадлежит к другому домену, и он будет использоваться для других целей.

Способ предотвращения наследования "реализации" находится на языке программирования с самого начала, вы должны использовать последнее ключевое слово в объявлении класса и таким образом запрещать подклассы.

Подклассификация не является злом, если это сделано специально. Если это произойдет, это может стать кошмаром. Я бы сказал, что вы должны начинать как частное, так и "окончательное", насколько это возможно, и при необходимости делать вещи более открытыми и расширяемыми. Это также широко объясняется в презентации "Как правильно разработать API и почему это важно" См. [Хороший API]

Продолжайте читать статьи, и со временем и практикой (и большим терпением) эта вещь станет более ясной. Хотя иногда вам просто нужно сделать работу и скопировать/вставить код: P. Это нормально, если вы сначала попытаетесь сделать это хорошо.

Вот ссылки как от Джошуа Блоха (ранее работавшего в Sun в ядре java, теперь работающего в Google)


[Эффективное] Эффективная Java. Определенно лучшая java-книга, которую не новичок должен изучать, понимать и практиковать. Должно быть.

Эффективная Java


[Хороший API] Презентация, которая рассказывает о дизайне API, повторном использовании и смежных вопросах. Он немного длинный, но он стоит каждую минуту.

Как создать хороший API и почему это имеет значение

С уважением.


Обновление: взгляните на минуту 42 видеосвязи, которую я вам отправил. Он говорит об этой теме:

"Когда у вас есть два класса в общедоступном API, и вы думаете сделать его подклассом другого, например, Foo является подклассом Bar, спросите себя, есть ли каждая панель Foo?...

И в минувшую минуту он говорит о "повторном использовании кода" во время разговора о TimeTask.

Ответ 2

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

В статье вы разместили ссылку, автор показывает "нарушение" наследования с помощью Stack и ArrayList. Этот пример ошибочен, потому что Stack не является ArrayList, и поэтому наследование не должно использоваться. Пример такой же ошибочный, как String, расширяющий символ, или PointXY, расширяющий Number.

Прежде чем расширять класс, вы всегда должны выполнять тест "is_a". Поскольку вы не можете сказать, что каждый стек - это ArrayList, не будучи каким-то образом неправильным, тогда вы не должны быть inheirit.

Контракт для Stack отличается от контракта для ArrayList (или List), и стек не должен наследовать методы, которые не заботятся (например, get (int i) и add()). Фактически Stack должен быть интерфейсом с такими методами, как:

interface Stack<T> {
   public void push(T object);
   public T pop();
   public void clear();
   public int size();
}

Класс, такой как ArrayListStack, может реализовать интерфейс Stack и в этом случае использовать композицию (имеющую внутренний ArrayList), а не наследование.

Наследование не плохое, плохое наследование плохо.

Ответ 3

Вы также можете использовать композицию и шаблон стратегии. текст ссылки

public class Car
{
  private ICar _car;

  public void Move() {
     _car.Move();
  }
}

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

Ответ 4

Вы можете использовать composition. В вашем примере объект Car может содержать другой объект под названием Drivetrain. Метод car move() может просто вызвать метод drive() для его трансмиссии. Класс Drivetrain мог, в свою очередь, содержать такие объекты, как "Двигатель", "Передача", "Колеса" и т.д. Если вы структурировали свою иерархию классов таким образом, вы могли бы легко создавать автомобили, которые движутся по-разному, составляя их из разных комбинаций более простых частей (т.е. код повторного использования).

Ответ 5

Чтобы облегчить создание микшинов/композиции, взгляните на мои аннотации и обработчик аннотации:

http://code.google.com/p/javadude/wiki/Annotations

В частности, пример mixins:

http://code.google.com/p/javadude/wiki/AnnotationsMixinExample

Обратите внимание, что в настоящее время он не работает, если интерфейсы/типы делегированы для параметризованных методов (или параметризованных типов в методах). Я работаю над этим...

Ответ 6

Мне смешно ответить на мой собственный вопрос, но вот что-то мне показалось довольно интересным: Sather.

Это язык программирования без наследования реализации вообще! Он знает интерфейсы (называемые абстрактными классами без реализации или инкапсулированные данные), а интерфейсы могут наследовать друг друга (на самом деле они даже поддерживают множественное наследование!), Но класс может реализовывать только интерфейсы (абстрактные классы, сколько угодно) он не может наследовать другого класса. Однако он может включать "другой" класс. Это скорее концепция делегата. Включенные классы должны быть созданы в конструкторе вашего класса и уничтожены при уничтожении вашего класса. Если вы не перезаписываете методы, которые у них есть, ваш класс также наследует свой интерфейс, но не их код. Вместо этого создаются методы, которые просто переадресуют вызовы вашего метода на одинаково названный метод включенного объекта. Разница между включенными объектами и инкапсулированными объектами заключается в том, что вам не нужно создавать делегацию вперед самостоятельно, и они не существуют как независимые объекты, которые вы можете пройти, они являются частью вашего объекта и живут и умирают вместе с вашим объект (или более технически говорящий): память для вашего объекта и всех включенных была создана с помощью одного вызова вызова, того же блока памяти, вам просто нужно инициализировать их в вызове конструктора, тогда как при использовании реальных делегатов каждый из этих объектов вызывает собственный вызов вызова, имеет собственный блок памяти и живет полностью независимо от вашего объекта).

Язык не такой красивый, но мне нравится идея: -)

Ответ 7

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

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

Шаблоны проектирования от банды четырех

Ответ 8

Наследование не требуется для объектно-ориентированного языка.

Рассмотрим Javascript, который, возможно, более объектно-ориентированный, чем Java. Нет классов, просто объектов. Код повторно используется путем добавления существующих методов к объекту. Объект Javascript по существу представляет собой карту имен для функций (и данных), где исходное содержимое карты устанавливается прототипом, а новые записи могут быть добавлены к данному экземпляру "на лету".