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

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 и иметь доступ к (ограниченной) инкапсуляции и наследованию.

Raynos
источник
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!
}

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

Bjorn
источник
Вы можете значительно расширить «класс». Вы просто не можете получить доступ к локальным переменным color, r, x, yи drawCircle, привязанных к лексической областиCircle
Raynos
Это правда, вы можете, но вы не можете сделать это без создания группы «открытых» методов, добавленных в контекст, которые создаются каждый раз, когда вы создаете экземпляр.
Бьорн
Это очень странная модель, которую вы используете там. Circle.call()как конструктор? Похоже, вы пытаетесь описать «функциональные объекты» (неправильно) ...
Шон Макмиллан,
Это не то, что я когда-либо делал, я просто говорю, что можно снова и снова вызывать конструктор с помощью JavaScript, но не на языках с традиционными классами.
Бьорн
1
Но как эти различия «важны»? Я не вижу конструирование объектов путем вызова функции, не используя «новый», как «важный», просто небольшая разница в синтаксисе. Точно так же отсутствие личных переменных принципиально не отличается, только небольшая разница. Кроме того, вы можете иметь (что-то похожее на) частные переменные, как вы описываете.
Roel