Наследование на основе прототипов и классов
В JavaScript каждый объект является одновременно экземпляром и классом. Для наследования вы можете использовать любой экземпляр объекта в качестве прототипа.
В Python, C++ и т. Д. Существуют классы и экземпляры как отдельные понятия. Чтобы выполнить наследование, вы должны использовать базовый класс для создания нового класса, который затем можно использовать для создания производных экземпляров.
Почему JavaScript пошел в этом направлении (ориентация объекта на основе прототипа)? Каковы преимущества (и недостатки) ОО на основе прототипа по сравнению с традиционным ОО на основе классов?
4 ответа
Здесь есть около сотни терминологических проблем, в основном построенных вокруг кого-то (не вас), пытающегося сделать свою идею звучащей как The Best.
Все объектно-ориентированные языки должны уметь работать с несколькими концепциями:
- инкапсуляция данных вместе со связанными операциями с данными, иначе называемыми как члены данных и функции-члены, или как данные и методы.
- наследование, способность сказать, что эти объекты точно такие же, как и другой набор объектов, КРОМЕ этих изменений
- полиморфизм ("много форм"), при котором объект сам решает, какие методы следует запустить, чтобы вы могли зависеть от языка для правильной маршрутизации запросов.
Теперь, что касается сравнения:
Во-первых, вопрос "класс" против "прототипа". Идея изначально возникла в Simula, где с помощью метода на основе классов каждый класс представлял набор объектов, которые разделяли одно и то же пространство состояний (читайте "возможные значения") и одни и те же операции, формируя, таким образом, класс эквивалентности. Если вы посмотрите на Smalltalk, так как вы можете открыть класс и добавить методы, это практически то же самое, что вы можете сделать в Javascript.
Позже ОО-языки хотели использовать статическую проверку типов, поэтому мы получили представление о фиксированном классе, установленном во время компиляции. В открытой версии у вас было больше гибкости; в более новой версии у вас была возможность проверить некоторые виды корректности в компиляторе, которые в противном случае потребовали бы тестирования.
На "классовом" языке это копирование происходит во время компиляции. В языке прототипа операции хранятся в структуре данных прототипа, которая копируется и изменяется во время выполнения. Однако, абстрактно, класс по-прежнему является классом эквивалентности всех объектов, которые используют одно и то же пространство состояний и методы. Когда вы добавляете метод к прототипу, вы фактически создаете элемент нового класса эквивалентности.
Теперь, зачем это? прежде всего потому, что он обеспечивает простой, логичный, элегантный механизм во время выполнения. Теперь, чтобы создать новый объект или создать новый класс, вам просто нужно выполнить глубокое копирование, копируя все данные и структуру данных прототипа. Вы получаете наследование и полиморфизм более или менее бесплатно: поиск метода всегда состоит из запроса словаря для реализации метода по имени.
Причина, по которой появился скрипт Javascript/ECMA, заключается в том, что, когда мы начинали с этим 10 лет назад, мы имели дело с гораздо менее мощными компьютерами и гораздо менее сложными браузерами. Выбор метода на основе прототипа означал, что интерпретатор может быть очень простым, сохраняя при этом желательные свойства ориентации объекта.
Сравнение, слегка смещенное в сторону подхода, основанного на прототипах, можно найти в статье " Я: сила простоты". В статье приводятся следующие аргументы в пользу прототипов:
Создание путем копирования. Создание новых объектов из прототипов осуществляется с помощью простой операции копирования, с простой биологической метафорой, клонирования. Создание новых объектов из классов выполняется путем создания экземпляра, который включает в себя интерпретацию информации о формате в классе. Инстанциация похожа на строительство дома по плану. Копирование привлекает нас как более простую метафору, чем инстанцирование.
Примеры уже существующих модулей. Прототипы являются более конкретными, чем классы, потому что они являются примерами объектов, а не описаниями формата и инициализации. Эти примеры могут помочь пользователям повторно использовать модули, упрощая их понимание. Система, основанная на прототипах, позволяет пользователю исследовать типичного представителя, а не требовать от него понимания его описания.
Поддержка уникальных объектов. Self предоставляет структуру, которая может легко включать в себя уникальные объекты со своим поведением. Поскольку каждый объект имеет именованные слоты, и слоты могут содержать состояние или поведение, любой объект может иметь уникальные слоты или поведение. Системы на основе классов предназначены для ситуаций, когда существует много объектов с одинаковым поведением. Не существует лингвистической поддержки, чтобы объект обладал своим собственным уникальным поведением, и было бы неудобно (подумайте о модели Singleton) создать класс, который гарантированно имеет только один экземпляр. Самость не страдает ни от одного из этих недостатков. Любой объект может быть настроен с его собственным поведением. Уникальный объект может содержать уникальное поведение, и отдельный "экземпляр" не требуется.
Устранение мета-регресса. Ни один объект в системе на основе классов не может быть самодостаточным; другой объект (его класс) необходим для выражения его структуры и поведения. Это приводит к концептуально бесконечной метарегрессии: point
это экземпляр классаPoint
, который является экземпляром метакласса Point
, который является экземпляром metametaclassPoint
, до бесконечности. С другой стороны, в системах на основе прототипов объект может включать свое собственное поведение; никакой другой объект не нужен, чтобы вдохнуть в него жизнь. Прототипы устраняют мета-регресс.
Self, вероятно, является первым языком для реализации прототипов. (Он также впервые применил другие интересные технологии, такие как JIT, которая позже появилась в JVM. Поэтому чтение других статей Self также должно быть поучительным).
Вы должны проверить большую книгу по JavaScript от Дугласа Крокфорда. Он дает очень хорошее объяснение некоторых дизайнерских решений, принятых создателями JavaScript.
Одним из важных аспектов проектирования JavaScript является его система наследования прототипов. Объекты являются гражданами первого класса в JavaScript, так что обычные функции также реализованы как объекты (точнее, объект "Функция"). По моему мнению, когда он изначально был разработан для запуска внутри браузера, он должен был использоваться для создания множества одноэлементных объектов. В браузере DOM вы найдете это окно, документ и т. Д. Все одноэлементные объекты. Кроме того, JavaScript является свободно типизированным динамическим языком (в отличие от, скажем, Python, который строго типизирован, динамический язык), в результате концепция расширения объекта была реализована с использованием свойства "prototype".
Так что я думаю, что есть некоторые плюсы для ОО на основе прототипов, реализованных в JavaScript:
- Подходит для свободно типизированных сред, нет необходимости определять явные типы.
- Упрощает реализацию шаблона синглтона (сравните JavaScript и Java в этом отношении, и вы поймете, о чем я говорю).
- Предоставляет способы применения метода объекта в контексте другого объекта, динамического добавления и замены методов из объекта и т. Д. (То, что невозможно в строго типизированных языках).
Вот некоторые из минусов прототипа OO:
- Нет простого способа реализации частных переменных. Можно реализовать частные переменные с использованием волшебства Крокфорда, используя замыкания, но это определенно не так тривиально, как использование частных переменных, скажем, в Java или C#.
- Я пока не знаю, как реализовать множественное наследование (для чего оно стоит) в JavaScript.
Разница между основными языками на основе классов ООП, такими как С# или java, и базовыми языками-прототипами, такими как javascript, заключается в возможности изменять типы объектов во время выполнения, в то время как в С# или java они отказались от этой возможности в пользу статической проверки типов, сделав классы фиксированными. во время компиляции. JS всегда был ближе к первому оригинальному дизайну ООП Алана Кея и таким языкам, как Smalltalk или симулятор.
это достигается за счет того, что сам план является экземпляром, типы на основе прототипа - это фактические экземпляры, к которым можно получить доступ и изменить их во время выполнения, в Javascript это очень просто с использованием объекта-прототипа, поскольку каждый тип объекта имеет этот объект.
пример: типfuncName.prototype.myNewMethod= function{ console.log("hello world")}