наследование на основе прототипов и классов

208

В JavaScript каждый объект является одновременно экземпляром и классом. Для наследования вы можете использовать любой экземпляр объекта в качестве прототипа.

В Python, C ++ и т. Д. Существуют классы и экземпляры как отдельные понятия. Чтобы выполнить наследование, вы должны использовать базовый класс для создания нового класса, который затем можно использовать для создания производных экземпляров.

Почему JavaScript пошел в этом направлении (ориентация объекта на основе прототипа)? Каковы преимущества (и недостатки) ОО на основе прототипа по сравнению с традиционным ОО на основе классов?

Стефано Борини
источник
10
На JavaScript оказал влияние Self, который был первым языком с прототипным наследованием. В то время классическое наследие было в моде, впервые появилось в Симуле. Однако классическое наследование было слишком сложным. Затем Дэвид Унгар и Рэндалл Смит после прочтения ГЭБ получили прозрение: «Наиболее конкретное событие может служить общим примером класса событий». Они поняли, что классы не требуются для объектно-ориентированного программирования. Следовательно, Я был рожден. Чтобы узнать, как наследование прототипов лучше, чем классическое наследование, прочитайте это: stackoverflow.com/a/16872315/783743 =)
Аадит М Шах,
@AaditMShah Что / кто GEB?
Алекс
3
@Alex GEB - книга, написанная Дугласом Хофштадтером. Это аббревиатура от Гёделя Эшера Баха. Курт Гедель был математиком. Эшер был художником. Бах был пианистом.
Аадит М Шах

Ответы:

201

Здесь есть около сотни терминологических проблем, в основном построенных вокруг кого-то (не вас), пытающегося сделать свою идею похожей на The Best.

Все объектно-ориентированные языки должны уметь работать с несколькими понятиями:

  1. инкапсуляция данных вместе со связанными с ними операциями над данными, по-разному известными как члены данных и функции-члены, или как данные и методы, среди прочего.
  2. наследование, способность сказать, что эти объекты точно такие же, как и другой набор объектов, КРОМЕ этих изменений
  3. полиморфизм («много форм»), при котором объект сам решает, какие методы следует запустить, чтобы вы могли зависеть от языка для правильной маршрутизации запросов.

Теперь, что касается сравнения:

Во-первых, весь вопрос "класса" против "прототипа". Идея изначально возникла в Simula, где с помощью метода на основе классов каждый класс представлял набор объектов, которые разделяли одно и то же пространство состояний (читайте «возможные значения») и одни и те же операции, формируя, таким образом, класс эквивалентности. Если вы посмотрите на Smalltalk, так как вы можете открыть класс и добавить методы, это практически то же самое, что вы можете сделать в Javascript.

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

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

Теперь, зачем это? прежде всего потому, что он обеспечивает простой, логичный, элегантный механизм во время выполнения. Теперь, чтобы создать новый объект или создать новый класс, вам просто нужно выполнить глубокое копирование, копируя все данные и структуру данных прототипа. Вы получаете наследование и полиморфизм более или менее бесплатно: поиск метода всегда состоит из запроса словаря для реализации метода по имени.

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

Чарли Мартин
источник
1
Правильно, этот парагаф читается так, как будто я имел в виду иначе? Даль и Никвист придумали «класс» как совокупность вещей с одинаковой сигнатурой метода.
Чарли Мартин
1
Это изменение говорит это лучше?
Чарли Мартин
2
Нет, извините, CLOS относится к концу 80-х гг. Dreamsongs.com/CLOS.html Smalltalk 1980 г. en.wikipedia.org/wiki/Smalltalk и Simula с полной ориентацией объекта 1967-68 en.wikipedia.org/wiki/Simula
Чарли Мартин
3
@ Stephano, Они не так отличны, как все: Python, Ruby, Smalltalk используют словари для поиска методов, а javascript и Self имеют классы. В какой-то степени можно утверждать, что различие заключается лишь в том, что ориентированные на прототипы языки демонстрируют свои реализации. Так что, вероятно, не стоит превращать это в «Большую сделку»: это скорее похоже на спор между EMACS и vi.
Чарли Мартин
21
Полезный ответ . +1 Менее полезный хлам в комментариях. Я имею в виду, имеет ли значение, был ли CLOS или Smalltalk первым? Большинство людей здесь не являются историками в любом случае.
Адам Арольд
40

Сравнение, которое слегка смещено в сторону подхода, основанного на прототипах, можно найти в статье « Самость: сила простоты» . В статье приводятся следующие аргументы в пользу прототипов:

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

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

Поддержка уникальных объектов . Self обеспечивает структуру, которая может легко включать в себя уникальные объекты со своим поведением. Поскольку каждый объект имеет именованные слоты, а слоты могут содержать состояние или поведение, любой объект может иметь уникальные слоты или поведение. Системы на основе классов предназначены для ситуаций, когда существует много объектов с одинаковым поведением. Не существует лингвистической поддержки для объекта, чтобы обладать его собственным уникальным поведением, и было бы неудобно создавать класс, который гарантированно имеет только один экземпляр [ думаю, шаблон синглтона ]. Самость не страдает ни от одного из этих недостатков. Любой объект может быть настроен с его собственным поведением. Уникальный объект может содержать уникальное поведение, и отдельный «экземпляр» не нужен.

Устранение мета-регресса . Ни один объект в системе на основе классов не может быть самодостаточным; другой объект (его класс) необходим для выражения его структуры и поведения. Это приводит к концептуально бесконечному мета-регрессу: a pointявляется экземпляром класса Point, который является экземпляром метакласса Point, который является экземпляром метаметакласса Pointдо бесконечности. С другой стороны, в системах на основе прототипов объект может включать свое собственное поведение; никакой другой объект не нужен, чтобы вдохнуть в него жизнь. Прототипы исключают метарегрессию.

Self , вероятно, является первым языком для реализации прототипов (он также ввел в действие другие интересные технологии, такие как JIT, которая позже попала в JVM), поэтому чтение других статей Self также должно быть поучительным.

Виджей Мэтью
источник
5
RE: Исключение мета-регресса: В Common Lisp Object System, которая основана на классах, a pointявляется экземпляром класса Point, который является экземпляром метакласса standard-class, который является экземпляром самого себя, ad finitum.
Макс Нанаси
Ссылки на самостоятельные документы мертвы. Рабочие ссылки: Я: сила простоты | Самостоятельная библиография
user1201917
24

Вы должны проверить большую книгу по JavaScript от Дугласа Крокфорда . Он дает очень хорошее объяснение некоторых дизайнерских решений, принятых создателями JavaScript.

Одним из важных аспектов проектирования JavaScript является его система наследования прототипов. Объекты являются первоклассными гражданами в JavaScript, настолько, что обычные функции также реализуются как объекты (точнее, объект 'Function'). По моему мнению, когда он изначально был разработан для работы внутри браузера, он должен был использоваться для создания множества одноэлементных объектов. В браузере DOM вы найдете это окно, документ и т. Д. Все одноэлементные объекты. Кроме того, JavaScript является свободно типизированным динамическим языком (в отличие от, скажем, Python, который строго типизирован, динамический язык), в результате концепция расширения объекта была реализована с использованием свойства «prototype».

Поэтому я думаю, что есть некоторые плюсы для ОО на основе прототипов, реализованных в JavaScript:

  1. Подходит для свободно типизированных сред, нет необходимости определять явные типы.
  2. Упрощает реализацию шаблона синглтона (сравните JavaScript и Java в этом отношении, и вы поймете, о чем я говорю).
  3. Предоставляет способы применения метода объекта в контексте другого объекта, динамического добавления и замены методов из объекта и т. Д. (То, что невозможно в строго типизированных языках).

Вот некоторые из минусов прототипа OO:

  1. Нет простого способа реализации частных переменных. Можно реализовать частные переменные с помощью волшебства Крокфорда , используя замыкания , но это определенно не так тривиально, как использование частных переменных, скажем, в Java или C #.
  2. Я пока не знаю, как реализовать множественное наследование (для чего оно стоит) в JavaScript.
Amit
источник
2
Просто используйте соглашение об именах для частных переменных, как это делает Python.
aehlke
1
в js способ делать приватные переменные - с помощью замыканий, и это не зависит от выбранного вами типа наследования.
Беня
6
Крокфорд много сделал для того, чтобы повредить JavaScript, потому что довольно простой язык сценариев превратился в притворное увлечение своими внутренними компонентами. У JS нет истинных частных областей ключевых слов или истинного множественного наследования: не пытайтесь их подделать.
Hal50000