diff --git a/1-js/8-oop/1-about-oop/article.md b/1-js/8-oop/1-about-oop/article.md index e03121f4..fc863b66 100644 --- a/1-js/8-oop/1-about-oop/article.md +++ b/1-js/8-oop/1-about-oop/article.md @@ -34,7 +34,13 @@ var vasya = new User("Вася"); // создали пользователя vasya.sayHi(); // пользователь умеет говорить "Привет" ``` -Здесь мы видим ярко выраженную сущность -- `User` (посетитель). +Здесь мы видим ярко выраженную сущность -- `User` (посетитель). Используя терминологию ООП, такие конструкторы часто называют *классами*, то есть можно сказать "класс `User`". + +[smart header="Класс в ООП"] +[Классом]("https://en.wikipedia.org/wiki/Class_(computer_programming)") в объектно-ориентированной разработке называют шаблон/программный код, предназначенный для создания объектов и методов. + +В JavaScript классы можно организовать по-разному. Говорят, что класс `User` написан в "функциональном" стиле. Далее мы также увидим "прототипный" стиль. +[/smart] ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, например [SOLID](https://ru.wikipedia.org/wiki/SOLID_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29). @@ -47,4 +53,4 @@ vasya.sayHi(); // пользователь умеет говор Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. -Здесь мы не имеем возможности углубиться в теорию ООП, но основных "китов", на которых оно стоит, потрогаем за усы вдумчиво и конкретно. +Здесь мы не имеем возможности углубиться в теорию ООП, поэтому чтение таких книг рекомендуется. Хотя основные принципы, как использовать ООП правильно, мы, всё же, затронем. \ No newline at end of file diff --git a/1-js/8-oop/5-functional-inheritance/article.md b/1-js/8-oop/5-functional-inheritance/article.md index c9811d45..16329d2b 100644 --- a/1-js/8-oop/5-functional-inheritance/article.md +++ b/1-js/8-oop/5-functional-inheritance/article.md @@ -17,17 +17,17 @@ Именно поэтому, увидев новую технику, мы уже можем что-то с ней сделать, даже не читая инструкцию. -**Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п.** +Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п. [smart header="В веб-разработке всё так же"] -В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса. +В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса. В них всех обычно есть что-то общее. -Можно выделить полезный общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. Это обычная практика, принятая во множестве библиотек. +Можно выделить такой общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. [/smart] ## Наследование от Machine -Например, у нас есть класс `Machine`, который реализует методы "включить" `enable()` и "выключить" `disable()`: +Базовый класс "машина" `Machine` будет реализовывать общего вида методы "включить" `enable()` и "выключить" `disable()`: ```js function Machine() { @@ -48,33 +48,33 @@ function Machine() { ```js function CoffeeMachine(power) { *!* - Machine.call(this); + Machine.call(this); // отнаследовать */!* + var waterAmount = 0; this.setWaterAmount = function(amount) { waterAmount = amount; }; - function onReady() { - alert('Кофе готово!'); - } - - this.run = function() { - setTimeout(onReady, 1000); - }; - } var coffeeMachine = new CoffeeMachine(10000); + +*!* coffeeMachine.enable(); +coffeeMachine.setWaterAmount(100); +coffeeMachine.disable(); +*/!* ``` -Наследование реализовано вызовом `Machine.call(this)` в начале `CoffeeMachine`. +Наследование реализовано вызовом `Machine.call(this)` в начале конструктора `CoffeeMachine`. Он вызывает функцию `Machine`, передавая ей в качестве контекста `this` текущий объект. `Machine`, в процессе выполнения, записывает в `this` различные полезные свойства и методы, в нашем случае `this.enable` и `this.disable`. -Далее `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы, а также пользоваться унаследованными. +Далее конструктор `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы. + +В результате мы получаем объект `coffeeMachine`, который включает в себя методы из `Machine` и `CoffeeMachine`. ## Защищённые свойства @@ -116,13 +116,13 @@ var coffeeMachine = new CoffeeMachine(10000); **Чтобы наследник имел доступ к свойству, оно должно быть записано в `this`.** -**При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`.** +При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`. ```js //+ run function Machine() { *!* - this._enabled = false; + this._enabled = false; // вместо var enabled */!* this.enable = function() { @@ -147,30 +147,19 @@ function CoffeeMachine(power) { var coffeeMachine = new CoffeeMachine(10000); ``` -**Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*.** +Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*. -Технически это, конечно, возможно, но приличный программист снаружи в такое свойство не полезет. - -**Вообще, это стандартная практика: конструктор сохраняет свои параметры в свойствах объекта. Иначе наследники не будут иметь к ним доступ.** +Технически, залезть в него из внешнего кода, конечно, возможно, но приличный программист так делать не будет. ## Перенос свойства в защищённые -В коде выше есть свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке: +У `CoffeeMachine` есть приватное свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке. ```js //+ run -function CoffeeMachine(power) { -*!* - Machine.apply(this, arguments); // (1) -*/!* - - alert(this._enabled); // false - alert(this._power); // 10000 -} - function Machine(power) { *!* - this._power = power; // (2) + this._power = power; // (1) */!* this._enabled = false; @@ -184,20 +173,29 @@ function Machine(power) { }; } +function CoffeeMachine(power) { +*!* + Machine.apply(this, arguments); // (2) +*/!* + + alert(this._enabled); // false + alert(this._power); // 10000 +} + var coffeeMachine = new CoffeeMachine(10000); ``` -В коде выше при вызове `new CoffeeMachine(10000)` в строке `(1)` кофеварка передаёт аргументы и контекст родителю вызовом `Machine.apply(this, arguments)`. +Теперь все машины `Machine` имеют мощность `power`. Обратим внимание, что мы из параметра конструктора сразу скопировали её в объект в строке `(1)`. Иначе она была бы недоступна из наследников. -Можно было бы использовать `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, мало ли, вдруг мы в будущем захотим их добавить. +В строке `(2)` мы теперь вызываем не просто `Machine.call(this)`, а расширенный вариант: `Machine.apply(this, arguments)`, который вызывает `Machine` в текущем контексте вместе с передачей текущих аргументов. -Далее конструктор `Machine` в строке `(2)` сохраняет `power` в свойстве объекта `this._power`, благодаря этому кофеварка, когда наследование перейдёт обратно к `CoffeeMachine`, сможет сразу обращаться к нему. +Можно было бы использовать и более простой вызов `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, вдруг их количество увеличится -- не надо будет переписывать. ## Переопределение методов Итак, мы получили класс `CoffeeMachine`, который наследует от `Machine`. -Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал. +Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал, то есть имеют мощность и их можно включать/выключать. Для этого достаточно вызвать `Machine` текущем контексте, а затем добавить свои методы. @@ -205,13 +203,15 @@ var coffeeMachine = new CoffeeMachine(10000); // Fridge может добавить и свои аргументы, // которые в Machine не будут использованы function Fridge(power, temperature) { - Machine.call(this, arguments); + Machine.apply(this, arguments); // ... } ``` -Кроме создания новых методов, можно заменить унаследованные на свои: +Бывает так, что реализация конкретного метода машины в наследнике имеет свои особенности. + +Можно, конечно, объявить в `CoffeeMachine` свой `enable`: ```js function CoffeeMachine(power, capacity) { @@ -224,7 +224,7 @@ function CoffeeMachine(power, capacity) { } ``` -...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя. Например, сделать так, чтобы при включении кофеварка тут же запускалась. +...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя, добавить к нему что-то. Например, сделать так, чтобы при включении кофеварка тут же запускалась. Для этого метод родителя предварительно копируют в переменную, и затем вызывают внутри нового `enable` -- там, где считают нужным: @@ -247,10 +247,10 @@ function CoffeeMachine(power) { **Общая схема переопределения метода (по строкам выделенного фрагмента кода):**