ООП в функциональном стиле
Объектно-ориентированное программирование в JavaScript
Объектно-ориентированная программа представляет собой совокупность логических конструктивных элементов – объектов. Все объекты являются экземплярами определенного класса.
Классы в свою очередь образуют иерархию наследования
Класс – универсальный тип данных, представляющий собой модель сущности: способ описания сущности, ее состояние, поведение, правила взаимодействия с данной сущностью, а также ее внутренний и внешний интерфейс
Классы представляют подход к проектированию, заменяющий процедурное программирование (с неизбежным “спагетти-кодом”) на хорошо структурированный, хорошо организованный код
Переменная-модель, относящаяся к определенному классу, называется экземпляром класса, или объектом
Традиционно выделяют три базовые концепции ООП: наследование, инкапсуляция, полиморфизм
JavaScript не является объектно-ориентированным языком, однако позволяет реализовать ООП- подобный подход, в частности, в функциональном стиле: роль классов выполняют функции- конструкторы
Наследование — позволяет описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.
Инкапсуляция — свойство языка программирования, позволяющее пользователю не задумываться о сложности реализации используемого программного компонента (что у него внутри?), а взаимодействовать с ним посредством предоставляемого интерфейса (публичных методов и членов), а также объединить и защитить жизненно важные для компонента данные. При этом пользователю предоставляется только спецификация (интерфейс) объекта.
Полиморфизм позволяет писать более абстрактные программы и повысить коэффициент повторного использования кода. Общие свойства объектов объединяются в систему, которую могут называть по-разному — интерфейс, класс.
Создание конструктора
Функция-конструктор применяется для создания объектов определенного типа. Вызов конструктора осуществляется с помощью оператора new
Вызов функции через оператор new создает новый объект
Технически, любая функция, вызванная при помощи оператора new, становится конструктором
По общему соглашению, имя функции-конструктора должно начинаться с заглавной буквы (User)
function User() {}
let person = new User(); // стандартом допускается let person = new User;
console.log(person); // User {}Свойства и методы класса записываются в объект this
Алгоритм работы конструктора: функция-конструктор создает новый пустой объект, this получает ссылку на этот объект, код функции выполняется, изменяя this, после чего функция возвращает новосозданный объект
function User(value) {
/* this = {}; – интерпретатор создает новый пустой объект */
/* модификация this: добавление свойств или методов */
this.name = value;
/* return this; – интерпретатор возвращает новый объект */
}
let person = new User('John Doe'); // User {name: "John Doe"}
alert(person.name); // "John Doe"С помощью функции-конструктора можно создать любое количество объектов по определенному в конструкторе образу и подобию
Возврат значения
Как правило, конструкторы ничего не возвращают при помощи оператора return. Конструктор автоматически возвращает новый объект, заполненный в this
Однако если в конструкторе присутствует return, то значение возвращается по следующим правилам:
- вызов return с объектом возвращает этот объект, а не this
- вызов return с примитивным значением возвращает this
function User(value) {
this.name = value;
this.age = 10;
return {name: 'Harry Potter'};
}
let person = new User('John Doe');
console.log(person); // {name: "Harry Potter"}function User(value) {
this.name = value;
this.age = 10;
return 'Harry Potter';
}
let person = new User('John Doe');
console.log(person); // {name: "John Doe", age: 10}Принцип ООП: инкапсуляция
Инкапсуляция решает задачу разделения внутреннего и внешнего интерфейса программы
Внутренний интерфейс (private) – свойства и методы, доступные только внутри класса (конструктора). Определяется посредством локальных переменных – объявленных через var, let, const
Внешний интерфейс (public) – методы и свойства, доступные конечному пользователю. Определяется через контекстное значение – this
В терминологии ООП инкапсуляция означает ограничение доступа к данным и сокрытие реализации методов с целью защиты их от несанкционированного доступа
Те данные и методы, которые должны быть доступны экземплярам конструктора (публичные данные), записываются в this, вспомогательные данные объявляют локальными.
Публичные свойства (public)
function User(value) {
this.name = value;
}
let person = new User('John Doe');
alert(person.name); // "John Doe"Приватные свойства (private)
function User(value) {
let name = value;
}
let person = new User('John Doe');
alert(person.name); // undefinedИнкапсуляция через замыкание
Ограничение доступа к данным класса реализуется через концепцию замыкания: параметры функции, а также переменные и методы, объявленные внутри класса, доступны извне только через публичные методы класса
function User(value) {
let name = value;
this.greeting = function() {
alert( 'Hello, ' + name );
};
}
let person = new User('John Doe');
person.greeting(); // "Hello, John Doe"
alert(person.name); // undefinedГеттеры и сеттеры
Для контроля над свойством или методом его делают приватным, а запись и чтение значения реализуются через специальные методы – getter и setter
function User(value) {
let name = value.trim() || 'Anonymous';
this.getName = function() {
return name;
};
this.setName = function(newName) {
if (!newName) alert('You forgot to specify a new name');
else name = newName;
};
}
let person = new User(' John Doe ');
alert( person.name ); // undefined
alert( person.getName() ); // "John Doe"
person.setName('');
alert( person.getName() ); // "John Doe"Принцип ООП: наследование
Наследование означает создание новых классов на основе существующих
Базовый класс (прототип) содержит методы и свойства, которые будут передаваться всем его потомкам. Производные классы (наследники) могут расширять или переопределять свой базовый функционал
Наследование реализуется с помощью методов передачи контекста
function User() {
let isActive = false;
this.activate = function() {
isActive = true;
};
}function Admin(value) {
User.call(this);
this.name = value;
}
let person = new Admin('John Doe');
person.activate();Защищенные свойства (protected)
Защищенные свойства (protected) доступны только внутри класса и для классов-потомков
В JavaScript нет специального механизма определения защищенных свойств класса. По общему соглашению, публичные свойства, начинающиеся со знака подчеркивания _, имеют статус защищенных
function User() {
this._isActive = false;
this.activate = function() {
this._isActive = true;
};
this.deactivate = function(){
this._isActive = false;
};
}
function Admin(value) {
User.call(this);
this.name = value;
this.isActivated = function() {
return this._isActive;
};
}
let person = new Admin('John Doe');
alert( person.isActivated() );Принцип ООП: полиморфизм
Полиморфизм как парадигма ООП подразумевает единый интерфейс и множество реализаций. Метод, определенный в базовом классе, может дополняться или перезаписываться в классе- наследнике
function User(value) {
this.name = value || '';
this._isActive = false;
this.activate = function() {
this._isActive = true;
};
this.deactivate = function() {
this._isActive = false;
};
}
function Admin(value) {
User.call(this, value);
/* переопределение родительского метода */
this.activate = function() {
this._isActive = 1;
};
/* расширение родительского метода */
let parentMethod = this.deactivate;
this.deactivate = function() {
let agree = confirm('Are you sure?');
if (agree) parentMethod.call(this);
};
}Схема функционального паттерна
- Создание базового конструктора, в котором могут быть приватные, защищенные и публичные свойства
function Parent() {
let privateProperty;
this._protectedProperty;
this.publicProperty;
}- Для наследования конструктор потомка использует методы call/apply
Parent.apply(this, arguments);- Наследник может перезаписать или расширить свойства родителя
let parentMethod = this.method;
this.method = function() {
parentMethod.call(this);
};Проверка принадлежности классу
Оператор instanceof позволяет проверить, принадлежит ли объект определенному классу, без учета наследования
function User(value) {
this.name = value || '';
}
function Admin(value) {
User.call(this, value);
}
let user = new Admin('John Doe');
console.log(user); // {name: "John Doe"}
console.log(user instanceof Admin); // true
console.log(user instanceof User); // falseМатериалы для прочтения
- ООП и реализация его принципов в функциональном стиле https://learn.javascript.ru/constructor-new