Я собираюсь продолжить разговор о прототипической объектной ориентации.
Но эта статья не является прямым продолжением другой статьи!
В нем могут быть повторяющиеся части!
Программирование на основе прототипов — это стиль объектно-ориентированного программирования, в котором повторное использование поведения (известное как наследование) осуществляется через процесс повторного использования существующих объектов, которые служат прототипами. Эта модель также может быть известна как прототипическое, прототип-ориентированное, бесклассовое или основанное на экземплярах программирование.
В языках, основанных на прототипах, нет явных классов. Объекты наследуются непосредственно от других объектов через свойство prototype. Свойство prototype называется prototype в Self и JavaScript или proto в Io. Существует два метода создания новых объектов: создание объекта («с нуля») или клонирование существующего объекта. Первый поддерживается с помощью некоторой формы объектных литералов, деклараций, в которых объекты могут быть определены во время выполнения с помощью специального синтаксиса, такого как {…}, и переданы непосредственно в переменную. Хотя большинство систем поддерживают различные способы клонирования, создание объектов с нуля не так заметно.
В языках, основанных на классах, новый экземпляр создается с помощью функции конструктора класса — специальной функции, которая резервирует блок памяти для членов объекта (свойств и методов) и возвращает ссылку на этот блок. Необязательный набор аргументов конструктора может быть передан в функцию и обычно хранится в свойствах. Полученный экземпляр наследует все методы и свойства, которые были определены в классе, который действует как шаблон, из которого можно построить объекты аналогичного типа.
Системы, поддерживающие создание объектов, позволяют создавать новые объекты с нуля, не клонируя существующий прототип. Эти системы предоставляют специальный синтаксис для указания свойств и поведения новых объектов без ссылок на существующие объекты. Во многих языках прототипов существует корневой объект, обычно с именем Object, который определяется как прототип по умолчанию для всех других объектов, создаваемых во время выполнения, и который несет в себе часто необходимые методы, такие как функция toString() для возврата описания объекта в виде строки. Полезным аспектом создания объекта является обеспечение того, чтобы имена слотов нового объекта (свойства и методы) не имели конфликтов с пространством имен объекта верхнего уровня. (В языке JavaScript это можно сделать, используя прототип null, то есть Object.create(null)).
Клонирование — это процесс, при котором новый объект создается путем копирования поведения существующего объекта (его прототипа). Затем новый объект приобретает все качества оригинала. С этого момента новый объект может быть изменен. В некоторых системах результирующий дочерний объект поддерживает явную связь (через делегирование или сходство) со своим прототипом, и изменения в прототипе делают соответствующие изменения очевидными в его клоне. Другие системы, такие как форт-подобный язык программирования Kevo, не распространяют изменения прототипов таким образом, а вместо этого следуют более конкатенативной модели, в которой изменения клонированных объектов не распространяются автоматически среди потомков.
Делегация
В языках, основанных на прототипах и использующих делегирование, среда выполнения языка способна отправить нужный метод или найти нужные данные, просто следуя серии указателей делегирования (от объекта к его прототипу), пока не будет найдено соответствие. Все, что необходимо для установления такого совместного использования поведения между объектами, — это указатель делегирования. В отличие от связи между классом и экземпляром в объектно-ориентированных языках, основанных на классах, связь между прототипом и его ветвями не требует, чтобы дочерний объект имел память или структурное сходство с прототипом помимо этой связи. Таким образом, дочерний объект может продолжать модифицироваться и изменяться с течением времени без реорганизации структуры связанного с ним прототипа, как в системах, основанных на классах. Важно также отметить, что не только данные, но и методы могут быть добавлены или изменены. По этой причине,
Конкатенация
При конкатенативном прототипировании — подходе, реализованном в языке программирования Kevo — не существует видимых указателей или ссылок на исходный прототип, из которого клонируется объект. Прототип (родительский) объекта копируется, а не связывается, и делегирование не происходит. В результате изменения в прототипе не будут отражены в клонированных объектах. [5]
Основное концептуальное различие при таком расположении заключается в том, что изменения, внесенные в объект-прототип, не распространяются автоматически на клоны. Это можно рассматривать как преимущество или недостаток. (Однако Kevo предоставляет дополнительные примитивы для публикации изменений в наборах объектов на основе их сходства — так называемые семейные сходства или механизм клонирования семей [5]- а не по таксономическому происхождению, как это характерно для модели делегирования). Также иногда утверждается, что прототипирование на основе делегирования имеет дополнительный недостаток, поскольку изменения в дочернем объекте могут повлиять на дальнейшую работу родительского. Однако эта проблема не присуща модели на основе делегирования и не существует в языках на основе делегирования, таких как JavaScript, которые гарантируют, что изменения дочернего объекта всегда записываются в самом дочернем объекте и никогда в родительском (т.е. значение дочернего объекта затеняет значение родительского, а не изменяет значение родительского).
В упрощенной реализации конкатенативное прототипирование будет иметь более быстрый поиск членов, чем прототипирование на основе делегирования (потому что нет необходимости следовать цепочке родительских объектов), но, наоборот, будет использовать больше памяти (потому что копируются все слоты, а не один слот, указывающий на родительский объект). Однако более сложные реализации могут избежать этой проблемы, хотя при этом необходимо найти компромисс между скоростью и памятью. Например, системы с конкатенативным прототипированием могут использовать реализацию copy-on-write, чтобы обеспечить обмен данными за кадром — и этот подход фактически используется в Kevo. [6] С другой стороны, системы с прототипированием на основе делегирования могут использовать кэширование для ускорения поиска данных.
Традиционные ОО-языки, основанные на классах, базируются на глубоко укоренившемся дуализме:
Классы определяют основные качества и поведение объектов.
Экземпляры объектов являются конкретными проявлениями класса. Программирование на основе прототипов использует обобщенные объекты, которые можно клонировать и расширять.
В языке ООП, основанном на классах, наследование и инстанцирование выглядят как две разные и самостоятельные вещи. Когда вы определяете подкласс, вы создаете новый класс, который наследует члены и поведение от своего базового класса, а затем расширяете его. Затем, если вам нужно манипулировать состоянием и данными только что созданного подкласса, вам нужно его инстанцировать. Тогда, и только тогда, вы можете относиться к нему как к объекту.
В случае прототипического ООП действия «наследования» и «инстанцирования» выглядят как один и тот же процесс. Понятия «класс» не существует, поэтому вы просто пропускаете процесс определения класса. Есть только объекты. Вы инстанцируете их во время создания.
Например, с точки зрения ООП, основанного на классах, Fruit и apple являются базовым и дочерним классами, соответственно.
Вообще, Fruit — это абстракция, которая описывает общий «фрукт» и не может быть инстанцирована. apple — это конкретный класс, который может быть инстанцирован, принимает все свойства Fruit и расширяет их как специальные свойства для «яблока».
В случае ООП на основе прототипов фрукт не является абстракцией, это конкретный объект, как и яблоко, поэтому его можно положить в корзину (массив), выполнить над ним операцию «съесть» или отправить другу (использовать в качестве аргумента). Это не кажется реалистичным, так как в реальной жизни вы не можете сделать это с фруктами, потому что нет абстрактных «фруктов», только конкретные.
Все объектно-ориентированные языки должны уметь работать с несколькими концепциями:
Инкапсуляция данных вместе с соответствующими операциями над данными, также известными как члены данных и функции-члены, или как данные и методы, среди прочего.
Наследование, возможность сказать, что эти объекты точно такие же, как этот другой набор объектов, ИСКЛЮЧАЯ эти изменения.
полиморфизм («много способов»), когда объект сам решает, какие методы выполнять, поэтому вы можете положиться на то, что язык правильно направит ваши запросы.
Вот что есть в прототипических языках
Например, предположим, что объекты класса Vehicle имеют имя и способность выполнять различные действия, например, ездить на работу и доставлять строительные материалы. «Автомобиль Боба» — это конкретный объект (экземпляр) класса Vehicle.
С названием «Машина Боба». Теоретически, можно послать сообщение «машине Боба», велев ей доставить строительные материалы.
Этот пример демонстрирует одну из проблем такого подхода: автомобиль Боба, который, как оказалось, является спортивным автомобилем, не способен перевозить и доставлять строительные материалы (в любом значимом смысле), но именно эту способность смоделировал класс Vehicle.
Более полезная модель возникает при использовании подклассов для создания специализаций транспортных средств, например, спортивных, Care, Flatbed и Truck. Только объекты классов Flatbed и Truck должны обеспечивать механизм доставки строительных материалов;
Спортивные автомобили, которые не подходят для этого вида работ, должны ездить только быстро. Однако эта более глубокая модель требует большего понимания в процессе проектирования, понимания, которое может проявиться только «по мере возникновения проблем».
Этот вопрос является одним из мотивирующих факторов для создания прототипов. Если нельзя с уверенностью предсказать, какими качествами будет обладать набор объектов и классов в отдаленном будущем, нельзя правильно спроектировать иерархию классов.
Очень часто в программу со временем нужно было добавить поведенческие модели, а разделы системы нужно было перепроектировать (или рефакторить), чтобы по-другому разделить объекты.
Опыт ранних ОО-языков, таких как Smalltalk, показал, что подобная проблема возникала снова и снова. Системы имели тенденцию расти до определенного момента, а затем становились слишком жесткими, поскольку базовые классы под кодом программиста становились просто «неправильными». Без возможности легко изменить исходный класс могут возникнуть серьезные проблемы.
Динамические языки, такие как Smalltalk, позволяли вносить такие изменения с помощью хорошо известных методов на классах;
При изменении класса объекты, основанные на нем, изменят свое поведение. Однако эти изменения нужно было делать очень осторожно, потому что другие объекты, основанные на том же классе, могли ожидать такого «неправильного» поведения: «неправильное» обычно зависит от контекста (это одна из форм проблемы хрупкого базового класса).
Кроме того, в таких языках, как C++, где подклассы могут компилироваться отдельно от суперклассов, изменение в суперклассе может фактически сломать предварительно скомпилированные методы подкласса.
В Self и других языках, основанных на прототипах, дуализм между классами и экземплярами объектов устранен.
Вместо того чтобы иметь «экземпляр» объекта, основанный на некотором «классе», в Self вы создаете копию существующего объекта и изменяете ее. Таким образом, «автомобиль Боба» будет создан путем создания копии существующего объекта «Vehicle», а затем добавления метода «drive fast», моделируя тот факт, что это Porsche 911.
Базовые объекты, которые в основном используются для создания копий, известны как прототипы. Эта техника, как утверждается, значительно упрощает динамику. Если существующий объект (или набор объектов) оказывается неадекватной моделью, программист может просто создать модифицированный объект с правильным поведением и использовать его вместо него. Код, использующий существующие объекты, не изменяется.
На примере фруктов, объект «фрукты» будет представлять свойства и функциональность фруктов в целом. Объект «банан» будет клонирован из объекта «фрукт», и к нему будут присоединены общие свойства, специфичные для банана.
Каждый отдельный объект «банан» будет клонирован из общего объекта «банан». Сравните это с парадигмой, основанной на классах, где класс «фрукты» будет расширен классом «бананы».
Во-первых, это вопрос «класс» против «прототипа». Идея изначально зародилась в Simula, где при использовании метода, основанного на классах, каждый класс представлял собой набор объектов, которые разделяли одно и то же пространство состояний (читай «возможные значения») и одни и те же операции, образуя, таким образом, класс эквивалентности. Если вы вспомните Smalltalk, то поскольку вы можете открыть класс и добавить методы, это фактически то же самое, что вы можете сделать в JavaScript.
Более поздние ОО-языки хотели иметь возможность использовать статическую проверку типов, поэтому у нас появилось понятие фиксированного класса, определяемого во время компиляции. В версии с открытыми классами у вас было больше гибкости; в более поздней версии у вас была возможность проверять в компиляторе на корректность некоторые типы, которые в противном случае требовали бы тестирования.
В языке, основанном на классах, это копирование происходит во время компиляции. В языке-прототипе операции хранятся в структуре данных прототипа, которая копируется и изменяется во время выполнения. Абстрактно, однако, класс по-прежнему является классом эквивалентности всех объектов, которые разделяют одно и то же пространство состояний и методы. Добавляя метод к прототипу, вы фактически создаете элемент нового класса эквивалентности.
Теперь, зачем это делать? В основном потому, что это создает простой, логичный и элегантный механизм выполнения. Теперь, чтобы создать новый объект или новый класс, вы просто выполняете глубокое копирование, копируя все данные и структуру данных прототипа.
Вы получаете наследование и полиморфизм более или менее бесплатно: поиск метода всегда состоит из запроса словаря на реализацию метода по имени.
Преимущества прототипов:
Создание путем копирования. Создание новых объектов на основе прототипов осуществляется с помощью простой операции — копирования, в простой биологической метафоре — клонирования. Создание новых объектов из классов осуществляется путем инстанцирования, которое включает интерпретацию информации о формате класса. Инстанцирование похоже на строительство дома по чертежу. Копирование кажется нам более простой метафорой, чем инстанцирование.
Примеры уже существующих модулей. Прототипы более конкретны, чем классы, потому что они являются примерами объектов, а не описаниями формата и инициализации. Эти примеры могут помочь пользователям повторно использовать модули, делая их более понятными. Система, основанная на прототипе, позволяет пользователю изучить типичного представителя, а не требует от него осмысления его описания.
Прототипы являются гибкими. Они могут быть изменяемыми или неизменяемыми.
Объекты могут наследоваться от нескольких прототипов.
Все просто. У вас есть только объекты, и расширение объектов — единственная необходимая операция.
Например, Self обеспечивает основу, которая может легко включать уникальные объекты с собственным поведением. Поскольку каждый объект имеет именованные слоты, а слоты могут содержать состояние или поведение, любой объект может иметь уникальные слоты или поведение. Системы, основанные на классах, предназначены для ситуаций, когда существует множество объектов с одинаковым поведением. Нет лингвистической поддержки для того, чтобы объект имел свое собственное уникальное поведение, и неудобно создавать класс, который гарантированно будет иметь только один экземпляр [ вспомните паттерн singleton ]. Self не страдает ни от одного из этих недостатков. Любой объект может быть настроен на свое собственное поведение. Уникальный объект может содержать уникальное поведение, и отдельный «экземпляр» не требуется.
Устранение мета-регрессии. Ни один объект в системе, основанной на классах, не может быть самодостаточным; для выражения его структуры и поведения требуется другой объект (его класс). Это приводит к концептуально бесконечной метарегрессии: точка является экземпляром класса Point, который является экземпляром метакласса Point, который является экземпляром метаметакласса Point, ad infinitum. С другой стороны, в системах, основанных на прототипах, объект может включать свое собственное поведение; для его оживления не требуется никакого другого объекта. Прототипы исключают мета-регрессию.
Self, вероятно, является первым языком, в котором были реализованы прототипы (он также стал пионером других интересных технологий, таких как JIT, которая позже вошла в JVM), поэтому чтение других статей о Self также должно быть поучительным.
\\\\\\\
Про javascript — ориентацию на прототипирование в интернете практически путают с js. Это самый используемый язык в мире с такой формой ооп.
Причина, по которой в сценариях JavaScript/ECMA появились прототипы, заключается в том, чтобы иметь дело с гораздо менее мощными компьютерами и гораздо менее сложными браузерами. Выбор метода, основанного на прототипах, означал, что синтаксический анализатор может быть очень простым, сохраняя при этом желательные объектно-ориентированные свойства.
Прототипическое наследование в JavaScript описывается Дугласом Крокфордом следующим образом:
Вы создаете объекты-прототипы, а затем… создаете новые экземпляры. Объекты в JavaScript являются изменяемыми, поэтому мы можем дополнять новые экземпляры, предоставляя им новые поля и методы. Затем они могут служить прототипами для еще более новых объектов. Нам не нужны классы для создания множества похожих объектов… Объекты наследуются от объектов. Что может быть более объектно-ориентированным, чем это?
Существует два метода создания новых объектов: создание объектов «с нуля» или клонирование существующего объекта.
В случае создания нового объекта все просто — если вам нужен новый объект, просто напишите новый объект. Нет необходимости описывать будущий объект понятием класса, вы описываете объект и его поведение в тот момент, когда вы его создаете. Это самое существенное отличие от ООП, основанного на классах.
Когда вы хотите «унаследовать» объект, вы клонируете его от существующего. Затем, по желанию, вы можете преобразовать и расширить этот вновь созданный объект в соответствии с вашими потребностями. После этого вы получите новый «подкласс» или, скорее, «под-объект» и одновременно его новый экземпляр.
Прототипическое наследование — это все об объектах. Объекты наследуют свойства от других объектов. Вот и все. Существует два способа создания объектов с использованием прототипического наследования в jvascript — делегирование и конкатенация.
С этого момента я буду использовать слово «клон» исключительно для обозначения наследования через делегирование, а слово «копия» — исключительно для обозначения наследования через конкатенацию.
Хватит разговоров. Давайте рассмотрим несколько примеров. Допустим, у меня есть круг радиусом 5:
var circle = {
radius: 5
};
Мы можем вычислить площадь и окружность круга по его радиусу:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Теперь я хочу создать еще одну окружность радиусом 10. Один из способов сделать это будет следующим
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
Однако JavaScript предоставляет лучший способ делегирования. Для этого используется функция Object.create:
var circle2 = Object.create(circle);
circle2.radius = 10;
Вот и все. Вы только что сделали прототипическое наследование в JavaScript. Разве это не так просто? Вы берете объект, клонируете его, изменяете то, что вам нужно, и вуаля — у вас новый объект.
Вы можете спросить: «Как это просто? Каждый раз, когда я хочу создать новый круг, мне приходится клонировать круг и вручную назначать радиус». Решение состоит в том, чтобы использовать функцию для выполнения тяжелой работы за вас:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
На самом деле, все это можно объединить в один объектный литерал следующим образом:
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
Если вы заметили в приведенной выше программе, функция create создает клон круга, присваивает ему новый радиус, а затем возвращает его. Это именно то, что делает конструктор в JavaScript:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
Шаблон конструктора в JavaScript является обратным прототипическому шаблону. Вместо того чтобы создавать объект, вы создаете конструктор. Ключевое слово new связывает указатель this внутри конструктора с клоном конструктора прототипа.
Звучит запутанно? Это потому, что шаблон конструктора в JavaScript излишне усложняет ситуацию. Именно это трудно понять большинству программистов.
Вместо того чтобы думать об объектах, наследующих от других объектов, они думают о конструкторах, наследующих от других конструкторов, и затем полностью запутываются.
Существует множество других причин, по которым следует избегать шаблона конструктора в JavaScript. Вы можете прочитать о них в моем посте здесь: Конструкторы и прототипы
Итак, каковы преимущества прототипического наследования перед классическим наследованием? Давайте еще раз рассмотрим наиболее распространенные аргументы и объясним, почему.
- Прототипическое наследование является простымCMS утверждает в своем ответе:
На мой взгляд, самым большим преимуществом прототипического наследования является его простота.
Давайте рассмотрим то, что мы только что сделали. Мы создали объект круг, радиус которого равен 5. Затем мы клонировали его и задали клону радиус 10.
Таким образом, нам нужны только две вещи, чтобы прототипическое наследование работало:
Способ создания нового объекта (например, литералы объектов).
Способ расширения существующего объекта (например, Object.create).
В отличие от этого, классическое наследование гораздо сложнее. При классическом наследовании у вас есть:
Занятия.
Объекты.
Интерфейсы.
Абстрактные классы.
Конструкторы.
общественность.
защищенный.
частный.
Вы поняли идею. Дело в том, что прототипическое наследование легче понять, легче реализовать и легче рассуждать о нем.
Как выразился Стив Егге в своей классической записи в блоге «Портрет новичка»:
Метаданные — это любое описание или модель чего-то другого. Комментарии в вашем коде — это просто описание вычислений на естественном языке. Что делает метаданные метаданными, так это то, что они не являются строго необходимыми. Если у меня есть собака с родословными документами, и я теряю эти документы, у меня все равно остается вполне пригодная собака.
В том же смысле классы — это просто метаданные. Классы не являются строго необходимыми для наследования. Однако некоторые люди (обычно новички) находят классы более удобными для работы. Это дает им ложное чувство безопасности.
Ну, мы также знаем, что статические типы — это просто метаданные. Они представляют собой специализированный тип комментариев, предназначенных для двух типов читателей: программистов и компиляторов. Статические типы рассказывают историю о вычислениях, предположительно для того, чтобы помочь обеим группам читателей понять замысел программы. Но статические типы можно отбросить во время выполнения, потому что в конечном итоге они являются просто стилизованными комментариями. Они похожи на документы о родословной: это может сделать определенный неуверенный тип личности более счастливым со своей собакой, но собаку это, конечно, не волнует.
Как я уже говорил ранее, занятия дают людям ложное чувство безопасности. Например, в Java вы получаете множество NullPointerExceptions, даже если ваш код вполне читабелен. Я думаю, что классическое наследование обычно мешает программированию, но, возможно, это касается только Java. В Python есть потрясающая классическая система наследования.
- Прототипическое наследование является мощнымБольшинство программистов, пришедших из классической среды, утверждают, что классическое наследование является более мощным, чем прототипическое наследование, потому что оно имеет:
Частные переменные.
Множественное наследование.
Это утверждение неверно. Мы уже знаем, что JavaScript поддерживает частные переменные с помощью закрытий, но как насчет множественного наследования? Объекты в JavaScript имеют только один прототип.
Правда в том, что прототипическое наследование поддерживает наследование нескольких прототипов. Прототипическое наследование просто означает, что один объект наследует от другого объекта. На самом деле существует два способа реализации прототипического наследования:
Делегирование или дифференцированное наследование
Клонирование или конкатенативное наследование
Да, JavaScript позволяет объектам только делегировать другому объекту. Однако он позволяет копировать свойства произвольного количества объектов. Например, _.extend делает именно это.
Конечно, многие программисты не считают это истинным наследованием, потому что в противном случае они говорят instanceof uo isPrototypeOf. Однако это можно легко исправить, сохранив массив прототипов для каждого объекта, который наследуется от прототипа через конкатенацию:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Таким образом, прототипическое наследование является таким же мощным, как и классическое наследование. На самом деле, оно гораздо мощнее классического наследования, потому что при прототипическом наследовании вы можете вручную выбирать, какие свойства копировать, а какие опускать из разных прототипов.
При классическом наследовании невозможно (или, по крайней мере, очень сложно) выбрать, какие свойства вы хотите унаследовать. Они используют виртуальные базовые классы и интерфейсы для решения проблемы алмазов.
Примечание: Проблема алмаза — это что-то о множественном наследовании, если вам интересно: https://pt.stackoverflow.com/questions/233728/heran%C3%A7a-m%C3%BAltipla-e-problema-do-diamante.
https://stringfixer.com/pt/Multiple_inheritance
Однако в JavaScript вы, скорее всего, никогда не услышите о проблеме алмаза, потому что вы можете точно контролировать, какие свойства и от каких прототипов вы хотите наследовать.
- Прототипическое наследование менее избыточноЭтот пункт немного сложнее объяснить, потому что классическое наследование не обязательно приводит к более избыточному коду. На самом деле, наследование, будь то классическое или прототипическое, используется для уменьшения избыточности в коде.
Одним из аргументов может быть то, что большинство языков программирования с классическим наследованием являются статически типизированными и требуют от пользователя явного объявления типов (в отличие от Haskell, который имеет неявную статическую типизацию). Следовательно, это приводит к более детальному коду.
Java печально известна таким поведением. Я четко помню, как Боб Нистром упомянул следующий анекдот в своем блоге о Pratt Parsers :
Вам должен понравиться уровень бюрократии Java «пожалуйста, подпишитесь в четырех экземплярах».
Опять же, я думаю, что это просто потому, что Java настолько плоха.
Весомым аргументом является то, что не все языки с классическим наследованием поддерживают множественное наследование. И снова на ум приходит Java. Да, в Java есть интерфейсы, но этого недостаточно. Иногда действительно необходимо многократное наследование.
Поскольку прототипическое наследование допускает множественное наследование, код, требующий множественного наследования, будет менее избыточным, если он написан с использованием прототипического наследования, а не на языке, который имеет классическое наследование, но не множественное наследование.
- Прототипическое наследование является динамическимОдно из самых важных преимуществ прототипического наследования заключается в том, что вы можете добавлять новые свойства к прототипам после их создания. Это позволяет добавлять новые методы в прототип, которые автоматически становятся доступными для всех объектов, делегированных этому прототипу.
Это невозможно в классическом наследовании, потому что после создания класса вы не можете изменить его во время выполнения. Это, вероятно, самое большое преимущество прототипического наследования над классическим, и оно должно быть на первом месте. Однако я люблю оставлять лучшее напоследок.
Заключение
Прототипическое наследование имеет значение. Важно просветить программистов JavaScript о том, почему следует отказаться от шаблона конструктора в пользу прототипического шаблона наследования.
Нам нужно начать преподавать JavaScript правильно, а это значит показать новым программистам, как писать код, используя паттерн прототипа вместо паттерна конструктора.
С помощью прототипического паттерна не только будет проще объяснить прототипическое наследование, но и сделать программистов лучше.
И прототипический паттерн, и паттерн конструктора (из js) эквивалентны. Поэтому вы можете задаться вопросом, зачем кому-то использовать прототипический шаблон вместо шаблона конструктора. В конце концов, шаблон конструктора более лаконичен, чем шаблон прототипа. Однако прототипический шаблон имеет много преимуществ перед шаблоном конструктора, перечисленных ниже:
Функциональные особенности можно использовать в сочетании с созданием.
Поскольку create — это функция, программа всегда будет работать так, как ожидается.
Прототипическое наследование является простым и понятным.
При использовании прототипического паттерна становится очевидным, что объект наследуется от другого объекта.
// Example of true prototypal inheritance style
// in JavaScript.
// object creation using the literal
// object notation {}.
const foo = { name: "foo", one: 1, two: 2 };
// Another object.
const bar = { two: "two", three: 3 };
// Object.setPrototypeOf() is a method introduced in ECMAScript 2015.
// For the sake of simplicity, let us pretend
// that the following line works regardless of the
// engine used:
Object.setPrototypeOf(bar, foo); // foo is now the prototype of bar.
// If we try to access foo's properties from bar
// from now on, we'll succeed.
bar.one; // Resolves to 1.
// The child object's properties are also accessible.
bar.three; // Resolves to 3.
// Own properties shadow prototype properties
bar.two; // Resolves to "two"
bar.name; // unaffected, resolves to "foo"
foo.name; // Resolves to "foo"
const foo = { one: 1, two: 2 };
// bar.[[prototype]] = foo
const bar = Object.create(foo);
bar.three = 3;
bar.one; // 1
bar.two; // 2
bar.three; // 3
Распространенная критика в адрес языков, основанных на прототипах, заключается в том, что сообщество разработчиков программного обеспечения не знакомо с ними, несмотря на популярность и проникновение на рынок JavaScript. Этот уровень знаний о системах, основанных на прототипах, похоже, растет с распространением фреймворков JavaScript и комплексным использованием JavaScript по мере развития Web.
\\\\\\\
Первым языком прототипирования был Self, на котором основан javascript.
Self был одним из первых языков, использовавших компиляцию «точно в срок» (JIT), которая позже была использована в JVM и веб-браузерах. Большая часть разработок Селфа произошла в Sun Microsystems.
Несколько методов компиляции «точно в срок» были впервые применены и усовершенствованы в рамках исследования Self, поскольку они были необходимы для того, чтобы объектно-ориентированный язык высокого уровня работал в два раза быстрее, чем оптимизированный C.
Жаль, что Sefl не добился большого успеха, но в итоге он вдохновил javascript, который стал очень успешным.
Примечание: Большая часть этой статьи является переводом с некоторыми изменениями.
Источники:
https://ericleads.wordpress.com/2013/02/11/fluent-javascript-three-different-kinds-of-prototypal-oo/
https://stackoverflow.com/questions/2800964/benefits-of-prototypal-inheritance-over-classical/16872315#16872315
http://aaditmshah.github.io/why-prototypal-inheritance-matters/
https://developer-interview.com/p/oop-ood/what-is-prototype-based-oop-how-it-is-different-from-class-based-4
https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_baseada_em_prot%C3%B3tipos
https://stackoverflow.com/questions/816071/prototype-based-vs-class-based-inheritance
https://algodaily.com/lessons/class-vs-prototypical-inheritance
https://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=AAC2A881EBA5031D9BD77FD77009F09B?doi=10.1.1.56.4713&rep=rep1&type=pdf
https://www.javatpoint.com/javascript-oops-prototype-object
https://developer-interview.com/p/oop-ood/how-prototype-based-oop-is-different-from-class-based-13
https://en.wikipedia.org/wiki/Self_(язык_программирования)
https://en.wikipedia.org/wiki/Prototype-based_programming
https://developer-interview.com/p/oop-ood/what-are-advantages-and-disadvantages-of-prototypal-oop-12
https://developer-interview.com/p/oop-ood/what-is-prototype-based-oop-how-it-is-different-from-class-based-4