Наследование в JavaScript. Функциональное наследование

Наследование — это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.

Класс (объект), от которого производится наследование, называется базовым, родительским или суперклассом (объектом).

Новый класс (объект) — потомком, наследником, дочерним или производным классом (объектом).

Наследование позволяет сокращать код, на каждом иерархическом шаге учитывая только изменения, не дублируя всё остальное, учтённое на предыдущих шагах.

JavaScript вместо наследования использует прототипирование: если искомого свойства или вызванного метода в самом объекте нет, то запрос передаётся объекту-прототипу (свойство prototype всех объектов JavaScript). При этом объект, от которого произошло наследование называется прототипом, и унаследованные свойства могут быть найдены в объекте prototype конструктора. Поведение всех объектов класса можно поменять, заменив один из методов прототипа (например, добавив метода .toBASE64 для класса String).

О классическом наследовании

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

Процесс классического наследования  должен создавать уровень абстракции, т.е. создается вертикальная иерархия.

При каждом наследовании, каждый дочерний класс должен снижать уровень абстракции, тем самым снижая уровень обобщения, например:

«ТУФЛИ» => «МУЖСКИЕ ТУФЛИ» => «МУЖСКИЕ ЛЕТНИЕ ТУФЛИ» => «МУЖСКИЕ ЛЕТНИЕ БЕЛЫЕ ТУФЛИ»

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

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

Задача программиста при использовании парадигмы классического наследования создать иерархию сущностей от максимальной общей к максимально конкретной.

При использование парадигмы прототипного наследования программист имеет дело только с объектами и при этом у него есть возможность создавать сущности в одном (горизонтальном) уровне абстракции.

[свернуть]

В JavaScript выделяют:

  1. функциональное наследование (через функции-конструкторы и классы class — с особенностями);
  2. прототипное наследование. 
Разница между наследованием через классы и прототипы

Классовое наследование

Создание класса с использованием ключевого слова class фактически является формой функции-конструктора, поэтому классовое наследование может как использовать, так и не использовать ключевое слово class из ES6.

Класс похож на шаблон – описание объекта который будет создан. Экземпляры классов обычно создаются через функции-конструкторы с помощью ключевого слова new

Классы наследуются от классов и создают дочерние связи: иерархическую классовую таксономию — самую сильную связанность доступную в объектной-ориентированной архитектуре.

Эти таксономии практически невозможно спроектировать правильно для всех новых сценариев. Широкое распространение (использование) базового класса приводит к проблеме «хрупкого базового класса» (сложность внесения правок, когда он начинает работать неправильно). Классовое наследование служит причиной многих проблем в ОО проектировании:

  1. проблема сильной связанности (классовое наследование является самым сильным связанным доступным в ОО проектировании);
  2. проблема хрупкого базового класса (неэластичная иерархия, в конечном итоге, все развивающиеся иерархии не будут работать для новых сценариев);
  3. дублирование кода (в связи с неэластичными иерархиями новые сценарии часто вклинены в нескольких местах, дублируя код);
  4. проблема «гориллы и банана» (вы хотели банан, но получили гориллу, ждущую банан, и целые джунгли в дополнение).

Прототипное наследование

Прототип — это экземпляр некоторого объекта, от которого наследуются напрямую другие объекты. Экземпляры могут быть скомпонованы из большого количества разных объектов, позволяя легко проводить точечное наследование и строить простую [[Prototype]] иерархию.

 

[свернуть]

Функциональное наследование в JS

Функциональное наследование использует вызов родительской функции-конструктора внутри функции-конструктора дочернего класса с одновременной передачей ей в качестве контекста this текущего объекта:

Наследование реализовано вызовом User.apply(this, arguments) в начале конструктора Klient(). Метод apply(this, arguments) вызывает функцию User, передавая ей в качестве контекста this текущий объект. Конструктор User в процессе выполнения записывает в this  свойства firstname, lastName  и метод getFullName.

Использование метода apply() вместо call() гарантирует передачу всех аргументов не в виде списка, а в виде массива (псевдомассива) аргументов: если их количество увеличится, то код не надо будет переписывать.

Далее конструктор Klient продолжает выполнение и может добавить свои свойства и методы, например, getLastName(). В результате мы получаем объект klient, который включает в себя методы из User и Klient.

Обратите внимание, наследник не имеет доступа к приватным свойствам родителя, например:

Чтобы наследник имел доступ к свойству родителя, оно должно быть записано в this, например, this._id = null; . При этом, чтобы обозначить что свойство является внутренним, его имя начинают с нижнего подчёркивания _.

Нижнее подчёркивание в начале именования свойства – общепринятый знак что свойство является внутренним (защищенным по соглашению, но доступным технически), предназначенным лишь для доступа из самого объекта и его наследников.

ВАЖНО! Методы родительского класса, вынесенные (определенные) в свойстве prototype, не наследуются!

Пример

Для решения данной проблемы необходимо:

  1. в prototype дочернего класса записать prototype родительского класса, используя метод Object.create();
  2. воcстановить значение constructor() дочернего класса, потерянное при перезаписи prototype.

Таким образом, код функции-конструктора дочернего класса будет следующим:

[свернуть]

Переопределение методов при функциональном наследовании

Бывает так, что реализация конкретного метода в наследнике имеет свои особенности, при этом, как правило, необходимо не заменить, а расширить метод родителя, добавить к нему какую-то функциональность. 

Для этого метод родителя предварительно копируют в переменную, и затем вызывают внутри метода дочернего класса (объекта) – там, где считают нужным:

Функциональное наследование в настоящее время используется нечасто, но знать и понимать его необходимо, так как во многих библиотеках классы написаны в функциональном стиле, и расширять (наследовать) их (от них) можно только так.

Классовое наследование

Классовое наследование в JavaScript можно рассматривать как функциональное наследование с расширенными возможностями: создание класса с использованием ключевого слова class фактически является формой функции-конструктора, поэтому классовое наследование может как использовать ключевое слово class из ES6, так и не использовать его (функциональное наследование через функции-конструкторы).

Класс похож на шаблон – описание объекта, который будет создан. Экземпляры классов обычно создаются с помощью ключевого слова new

Особенности классового наследования:

  1. для создания дочернего класса в объявлении класса (или в выражениях класса) применяется ключевое слово extends, использующее прототипы (prototype);
  2. для вызова функций, принадлежащих родителю объекта, используется ключевое слово super.

Ключевое слово extends позволяет наследовать методы родительского класса, вынесенные в prototype:

Ключевое слово super используется для вызова функций, принадлежащих родителю объекта. В конструкторе ключевое слово super() используется как функция, вызывающая родительский конструктор: её необходимо вызвать до первого обращения к ключевому слову this в теле конструктора:

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.