Статьи

Наследование и расширение объектов с помощью JavaScript

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

Существуют и другие шаблоны проектирования, которые мы можем использовать, чтобы сделать наши приложения более понятными и готовыми к изменениям. Я покажу вам примеры того, как вы можете использовать наследование, а также декоратор и составной шаблон для улучшения дизайна вашей программы.

Идея наследования заключается в том, что один объект «является» специализированной версией другого объекта. Существует родительский класс (также известный как суперкласс), который определяет базовые свойства нашего объекта. И есть дочерний класс (подкласс), который наследует свойства родительского класса.

Примером наследования является собака и пудель. Все собаки имеют определенные особенности, такие как четыре ноги и способность лаять. Пудель — это своего рода собака. Внедорожник — это транспортное средство. Круг — это форма. Так будет выглядеть наша иерархия классов, если мы разрабатываем программу для создания фигур.

Базовая диаграмма наследования

Преимущество наличия класса Shape состоит в том, что мы можем повторно использовать свойства и методы, которые мы определили в других классах.

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

Переопределение родительского метода в дочернем классе является примером полиморфизма. Полиморфизм — это способность объектов иметь несколько форм. Это позволяет подклассам иметь методы с тем же именем, что и их суперкласс, но с разными реализациями.

Это пример того, как наш код будет выглядеть для создания подкласса Shape и Circle:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class Shape {
    constructor(x, y) {
        this.xPosition = x;
        this.yPosition = y;
    }
 
getArea() {…}
 
}
 
class Circle extends Shape {
    constructor(x, y, radius) {
        super(x, y, radius);
        this.radius = radius
    }
 
    getArea() {…}
 
}
 
let circle = new Circle(1,2,3);

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

Например, предположим, что позже мы решили, что было бы лучше заменить параметры x и y класса Shape объектом. Следовательно, нам нужно будет изменить конструктор для всех наших подклассов и аргументы для каждого объекта, который мы создали.

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

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

Вместо того, чтобы думать, что круг — это форма, мы могли бы использовать класс Shape для создания кругов и обернуть его необходимыми дополнительными свойствами. Вот альтернатива созданию круговых объектов с использованием декоратора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class Shape {
    constructor(data) {
        this.x = data.x;
        this.y = data.y;
    }
 
    getArea() {…}
}
 
function CircleDecorator(shape) {
    shape.radius = 3;
    shape.getArea = function() {…};
    return shape;
}
 
let shape = new Shape({x:1,y:2});
let circle = new CircleDecorator(shape);

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

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

Например, у нас может быть класс, который обрабатывает регистрацию пользователей в нашей учетной записи. Прежде чем мы сохраним их информацию в нашей базе данных, было бы целесообразно проверить, что ввод действителен и не содержит вредоносных скриптов. Мы могли бы украсить класс методом, чтобы сначала проверить информацию. Этот же декоратор можно повторно использовать в любом месте нашего приложения, где мы принимаем пользовательский ввод.

Объекты могут состоять из других объектов. Представление для приложения можно рассматривать как компонент, состоящий из других представлений. Если бы мы создавали игру, наш игровой мир отображал бы всю нашу графику, которую мы создали, как круги и квадраты. Каждый раз, когда мы обновляем наше представление, нам нужно будет перерисовывать каждый элемент. Нам нужен способ управлять всеми элементами как группой.

Вот где нам может помочь составной шаблон. Мы можем создать класс, который отвечает за все элементы. Когда мы хотим перерисовать элементы, мы вызываем метод draw этого класса, и он будет вызывать метод draw для каждого отдельного элемента.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Component {
    constructor(name){
        this.components = [];
        this.element = document.createElement(name);
    }
 
    add(elem) {
        this.components.push(elem);
    }
 
    draw() {
        for (const elem of this.components) {
            elem.draw();
        }
    }
}
 
class Circle {
    constructor(data) {
        this.x = data.x;
        this.y = data.y;
        this.radius = data.radius;
    }
    getArea() {…}
    draw() {…}
}
 
let world = new Component(‘div’);
let circle = new Circle({x: 1, y:1, radius: 2});
let circle2 = new Circle({x: 10, y:10, radius: 2});
 
world.add(circle);
world.add(circle2);
world.draw();

Веб-страница также может рассматриваться как компонент. Этот компонент может иметь меню, боковую панель и сообщение в блоге. Пост будет субкомпонентом с изображением, заголовком и телом. Метод draw для нашего основного компонента приложения будет вызывать draw в меню, боковой панели и публикации. Компонент post, в свою очередь, вызывает рисование изображения, заголовка и тела сообщения.

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

Композитный паттерн

Этот шаблон не ограничивается созданием представлений. Например, если бы мы создавали игру, у нас мог бы быть компонент для управления элементами на экране, компонент для управления звуком и компонент для управления состоянием игры.

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

Наследование позволяет нам повторно использовать код, определяя объект в терминах другого объекта. Шаблон декоратора позволяет нам добавлять обязанности к объекту без изменения исходного кода или подклассов. Составной шаблон моделирует отношения часть-целое Эти шаблоны не предназначены для использования в изоляции.

JavaScript стал языком работы в сети. Это не без кривых обучения, и есть множество фреймворков и библиотек, которые также могут вас занять. Если вы ищете дополнительные ресурсы для обучения или использования в своей работе, посмотрите, что у нас есть на Envato Market .

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