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


20

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

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

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

Теперь это было article, saying it's better to inherit interfaces than implementations, вы можете прочитать его (по крайней мере, в первой половине первой страницы), это довольно интересно. Он избегает таких проблем, как fragile base class problem. Пока это имеет много смысла и многие другие вещи, сказанные в статье, имеют для меня большой смысл.

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

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

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

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

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

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

?

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

  0

Я, например, не нравится называть это «наследование интерфейса», но «реализация интерфейса». Это одна вещь, которая мне очень нравится на языке Java, две разные концепции, названные соответственно. 11 окт. 092009-10-11 21:40:20

11

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

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

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

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

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

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

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

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

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

Наследование - это больше, чтобы «специализироваться» на классах, таким же образом грузовик является специализированной версией автомобиля, а MosterTruck - специализированной версией Truck.

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

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

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

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

Вот ссылки как из Джошуа Блоха (ранее работающих в ВС на основе Java сейчас работает на Google)


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

Effective Java


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

How To Design A Good API and Why it Matters

С уважением.


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

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

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

  0

Серьезно, я не вижу, как грузовик должен унаследовать от автомобиля :) 11 окт. 092009-10-11 21:55:47

+2

Возможно, грузовики и автомобили должны унаследовать от Automobile. :-) 18 фев. 102010-02-18 22:05:38


4

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

public class Car 
{ 
    private ICar _car; 

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

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


2

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


0

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

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

Design Patterns by the Gang of Four


2

Чтобы Примеси/композиции проще, посмотрите на мои аннотаций и аннотаций Процессор:

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

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

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

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

  0

Это довольно интересный материал, который у вас есть. Я подробно рассмотрю его :) 23 сен. 082008-09-23 23:23:50


8

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

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

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

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

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

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

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


0

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

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


2

Забавно ответить на мой собственный вопрос, но вот что я нашел, что довольно интересно: Sather.

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

язык не очень красиво, но я люблю идею за ним :-)