Оба примера кода, которые вы продемонстрировали в своем вопросе, используют прототипное наследование. Фактически любой объектно-ориентированный код, который вы пишете на JavaScript, является парадигмой прототипного наследования. В JavaScript просто нет классического наследования. Это должно немного прояснить ситуацию:
Inheritance
|
+-----------------------------+
| |
v v
Prototypal Classical
|
+------------------------------+
| |
v v
Prototypal Pattern Constructor Pattern
Как видите, прототипное и классическое наследование - это две разные парадигмы наследования. Некоторые языки, такие как Self, Lua и JavaScript, поддерживают прототипное наследование. Однако большинство языков, таких как C ++, Java и C #, поддерживают классическое наследование.
Краткий обзор объектно-ориентированного программирования
И прототипное, и классическое наследование - это парадигмы объектно-ориентированного программирования (т.е. они имеют дело с объектами). Объекты - это просто абстракции, которые инкапсулируют свойства сущности реального мира (т.е. они представляют реальные словесные вещи в программе). Это называется абстракцией.
Абстракция: представление вещей реального мира в компьютерных программах.
Теоретически абстракция определяется как «общее понятие, образованное путем извлечения общих черт из конкретных примеров». Однако для объяснения мы будем использовать вместо этого вышеупомянутое определение.
Теперь у некоторых объектов много общего. Например, грязевой байк и Harley Davidson имеют много общего.
Грязевой байк:
Харлей Дэвидсон:
Грязевой байк и Харлей Дэвидсон - оба мотоцикла. Следовательно, байк - это обобщение как грязевого велосипеда, так и Harley Davidson.
Bike
|
+---------------------------------+
| |
v v
Mud Bike Harley Davidson
В приведенном выше примере байк, грязевой байк и Harley Davidson - все абстракции. Однако байк - это более общая абстракция от грязевого велосипеда и Harley Davidson (то есть и грязевой байк, и Harley Davidson являются конкретными типами велосипедов).
Обобщение: абстракция более конкретной абстракции.
В объектно-ориентированном программировании мы создаем объекты (которые являются абстракциями сущностей реального мира) и используем классы или прототипы для создания обобщений этих объектов. Обобщения создаются посредством наследования. Велосипед - это обобщение грязевого велосипеда. Следовательно, грязевые велосипеды унаследованы от мотоциклов.
Классическое объектно-ориентированное программирование
В классическом объектно-ориентированном программировании есть два типа абстракций: классы и объекты. Как упоминалось ранее, объект - это абстракция сущности реального мира. С другой стороны, класс - это абстракция объекта или другого класса (т. Е. Это обобщение). Например, рассмотрим:
+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity | Comments |
+----------------------+----------------+---------------------------------------+
| 0 | John Doe | Real World Entity. |
| 1 | johnDoe | Variable holding object. |
| 2 | Man | Class of object johnDoe. |
| 3 | Human | Superclass of class Man. |
+----------------------+----------------+---------------------------------------+
Как вы можете видеть в классических объектно-ориентированных языках программирования, объекты - это только абстракции (т.е. все объекты имеют уровень абстракции 1), а классы - только обобщения (т.е. все классы имеют уровень абстракции больше 1).
Объекты в классических объектно-ориентированных языках программирования могут быть созданы только путем создания экземпляров классов:
class Human {
// ...
}
class Man extends Human {
// ...
}
Man johnDoe = new Man();
Подводя итог, в классических объектно-ориентированных языках программирования объекты - это абстракции сущностей реального мира, а классы - это обобщения (т. Е. Абстракции объектов или других классов).
Следовательно, по мере увеличения уровня абстракции сущности становятся более общими, а по мере уменьшения уровня абстракции сущности становятся более конкретными. В этом смысле уровень абстракции аналогичен шкале от более конкретных сущностей до более общих.
Прототипное объектно-ориентированное программирование
Прототипные объектно-ориентированные языки программирования намного проще классических объектно-ориентированных языков программирования, потому что в прототипном объектно-ориентированном программировании у нас есть только один тип абстракции (т. Е. Объекты). Например, рассмотрим:
+----------------------+----------------+---------------------------------------+
| Level of Abstraction | Name of Entity | Comments |
+----------------------+----------------+---------------------------------------+
| 0 | John Doe | Real World Entity. |
| 1 | johnDoe | Variable holding object. |
| 2 | man | Prototype of object johnDoe. |
| 3 | human | Prototype of object man. |
+----------------------+----------------+---------------------------------------+
Как вы можете видеть в прототипах объектно-ориентированных языков программирования, объекты являются абстракциями либо сущностей реального мира (в этом случае они просто называются объектами), либо других объектов (в этом случае они называются прототипами тех объектов, которые они абстрагируют). Следовательно, прототип - это обобщение.
Объекты в прототипных объектно-ориентированных языках программирования могут быть созданы либо ex-nihilo (то есть из ничего), либо из другого объекта (который становится прототипом вновь созданного объекта):
var human = {};
var man = Object.create(human);
var johnDoe = Object.create(man);
По моему скромному мнению, прототипные объектно-ориентированные языки программирования более мощные, чем классические объектно-ориентированные языки программирования, потому что:
- Есть только один тип абстракции.
- Обобщения - это просто объекты.
К настоящему времени вы, должно быть, осознали разницу между классическим и прототипным наследованием. Классическое наследование ограничено классами, унаследованными от других классов. Однако прототипное наследование включает не только прототипы, унаследованные от других прототипов, но и объекты, унаследованные от прототипов.
Изоморфизм класса прототипа
Вы, наверное, заметили, что прототипы и классы очень похожи. Это правда. Они есть. На самом деле они настолько похожи, что вы можете использовать прототипы для моделирования классов:
function CLASS(base, body) {
if (arguments.length < 2) body = base, base = Object.prototype;
var prototype = Object.create(base, {new: {value: create}});
return body.call(prototype, base), prototype;
function create() {
var self = Object.create(prototype);
return prototype.hasOwnProperty("constructor") &&
prototype.constructor.apply(self, arguments), self;
}
}
Используя указанную выше CLASS
функцию, вы можете создавать прототипы, похожие на классы:
var Human = CLASS(function () {
var milliseconds = 1
, seconds = 1000 * milliseconds
, minutes = 60 * seconds
, hours = 60 * minutes
, days = 24 * hours
, years = 365.2425 * days;
this.constructor = function (name, sex, dob) {
this.name = name;
this.sex = sex;
this.dob = dob;
};
this.age = function () {
return Math.floor((new Date - this.dob) / years);
};
});
var Man = CLASS(Human, function (Human) {
this.constructor = function (name, dob) {
Human.constructor.call(this, name, "male", dob);
if (this.age() < 18) throw new Error(name + " is a boy, not a man!");
};
});
var johnDoe = Man.new("John Doe", new Date(1970, 0, 1));
Однако обратное неверно (т.е. вы не можете использовать классы для моделирования прототипов). Это потому, что прототипы являются объектами, а классы - не объектами. Это совершенно другой тип абстракции.
Вывод
Подводя итог, мы узнали, что абстракция - это «общее понятие, образованное путем извлечения общих черт из конкретных примеров», а обобщение - это «абстракция более конкретной абстракции» . Мы также узнали о различиях между прототипным и классическим наследованием и о том, что оба они являются двумя сторонами одной медали.
На прощание я хотел бы отметить, что существует два шаблона прототипного наследования: шаблон прототипа и шаблон конструктора. Шаблон прототипа - это канонический шаблон наследования прототипа, тогда как шаблон конструктора используется для того, чтобы сделать прототипное наследование более похожим на классическое наследование. Лично я предпочитаю прототипный паттерн.
PS Я тот парень, который написал сообщение в блоге « Почему имеет значение прототипное наследование » и ответил на вопрос « Преимущества прототипного наследования перед классическим? ». Мой ответ - принятый ответ.