Чем прототипное наследование практически отличается от классического наследования?


27

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

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

Чтобы очистить себя больше, я не вижу различий в полезности и моделях использования прототипа и классического наследования. Это приводит к тому, что мне не интересно узнавать, почему они разные, так как они оба приводят к одному и тому же, OOAD. Чем практически (не теоретически) наследование прототипа отличается от классического наследования?

Ответы:


6

Недавнее сообщение в блоге о JS OO

Я считаю, что вы сравниваете классическую OO-эмуляцию в JavaScript и классическую OO, и, конечно, вы не увидите никакой разницы.

Отказ от ответственности: замените все ссылки на «прототип OO» на «прототип OO в JavaScript». Я не знаю специфики Self или любой другой реализации.

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

Для прототипа OO нет понятия инкапсуляции. Инкапсуляция - это функция области видимости, замыканий и функций первого класса, но она не имеет ничего общего с прототипом ОО. Также нет понятия наследования, то, что люди называют «наследованием», на самом деле просто полиморфизм.

Тем не менее, он имеет полиморфизм.

Например

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Ясно, что dимеет доступ к методу, Dog.walkи это проявляет полиморфизм.

Так что на самом деле есть большая разница . У вас есть только полиморфизм.

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


1
@Rayons, Google для прототипного наследования, и вы увидите, что прототипное наследование подходит. Даже из Yahoo!
— Саид Нимати

Наследство @Saeed - расплывчатый термин, и его часто используют неправильно. Что они имеют в виду под «наследованием», я называю «полиморфизм». Определения слишком расплывчаты.
— Райнос

Нет @Rayons, я имел в виду слово « прототип» - это правильный термин, а не « прототип» . Я не говорил о наследовании :)
— Саид Нимати

1
@Saeed, это одна из тех опечаток, которые мой разум просто отключает. Я не
— обращаю

1
Хм .. терминология. Ик. Я бы назвал способ, которым объекты javascript связаны с их прототипами, «делегирование». Полиморфизм кажется мне проблемой более низкого уровня, основанной на том факте, что данное имя может указывать на разный код для разных объектов.
— Шон Макмиллан

15

Классическое наследование наследует поведение без какого-либо состояния от родительского класса. Он наследует поведение в момент создания объекта.

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

«Преимущество» наследования прототипов заключается в том, что вы можете «исправлять» состояние и поведение после создания всех ваших объектов. Например, в среде Ext JS обычно загружаются «переопределения», которые исправляют основные компоненты платформы после ее создания.


4
На практике, если вы наследуете состояние, вы настраиваете себя на мир боли. Унаследованное состояние станет общим, если оно живет на другом объекте.
— Шон Макмиллан

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

Значит, у Python нет классического наследования? Если у меня есть, class C(object): def m(self, x): return x*2а затем, instance = C()когда я бегу, instance.m(3)я получаю 6. Но если бы я тогда изменить Cтак C.m = lambda s, x: x*xи я бегу instance.m(3)я теперь получить 9. То же самое происходит, если я создаю class D(C)и изменяю метод на Cвсе экземпляры, которые также Dполучают измененный метод. Я неправильно понимаю или это означает, что Python не имеет классического наследования в соответствии с вашим определением?
— mVChr

@mVChr: ​​Вы все еще наследуете поведение, а не состояние в этом случае, что указывает на классическое наследование, но оно указывает на проблему с моим определением «наследует поведение в момент создания объекта». Я не совсем уверен, как исправить определение.
— Джоери Себрехтс

9

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

Второе: в большинстве прототипных сред используется тот же тип разделения, что и в средах на основе классов - изменяемые данные в экземпляре с наследуемыми методами. Так что опять очень мало различий. (См. Мой ответ на этот вопрос о переполнении стека и «Организация самостоятельной работы без классов . Посмотрите на Citeseer для PDF-версии» .)

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

В-четвертых: практические различия невелики, в то время как практическая проблема забвения в использовании newнамного больше - то есть, вы гораздо больше подвержены тому, что пропустите, newчем вы будете испытывать разницу между прототипом и классическим кодом ,

При этом практическое различие между прототипом и классическим наследованием заключается в том, что ваши методы "вещи, которые держат" (классы) совпадают с вашими вещами, которые держат данные (экземпляры). Это означает, что вы можете создавать свои классы по частям, используя все те же инструменты манипулирования объектами, которые вы использовали бы в любом случае. (Это, собственно, то, как это делают все библиотеки эмуляции классов. Для подхода, не совсем похожего на все остальные, посмотрите на Traits.js ). Это в первую очередь интересно, если вы занимаетесь метапрограммированием.


уу, лучший ответ ИМО!
— Айвар

0

Наследование прототипов в JavaScript отличается от классов следующими важными способами:

Конструкторы - это просто функции, которые вы можете вызывать без new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Здесь нет личных переменных или методов, в лучшем случае вы можете сделать это:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

В предыдущем примере вы не сможете существенно расширить класс, если вы прибегаете к фиктивным частным переменным и методам, и, кроме того, все объявленные вами публичные методы будут создаваться заново при каждом создании нового экземпляра.


Вы можете значительно расширить «класс». Вы просто не можете получить доступ к локальным переменным color, r, x, yи drawCircle, привязанных к лексической областиCircle
— Raynos

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

Это очень странная модель, которую вы используете там. Circle.call()как конструктор? Похоже, вы пытаетесь описать «функциональные объекты» (неправильно) ...
— Шон Макмиллан,

Это не то, что я когда-либо делал, я просто говорю, что можно снова и снова вызывать конструктор с помощью JavaScript, но не на языках с традиционными классами.
— Бьорн

1
Но как эти различия «важны»? Я не вижу конструирование объектов путем вызова функции, не используя «новый», как «важный», просто небольшая разница в синтаксисе. Точно так же отсутствие личных переменных принципиально не отличается, только небольшая разница. Кроме того, вы можете иметь (что-то похожее на) частные переменные, как вы описываете.
— Roel
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.