При написании чистого кода нужно быть осторожным с объектами, классами или конструкторами, которые мы объявляем. Мы не хотим предоставлять данные, которые не должны быть видны снаружи, и хотим определять структуры данных так, чтобы их легко можно было повторно использовать.
В этой статье мы рассмотрим, как организовать данные так, чтобы их было легко использовать.
Используйте классы вместо функций конструктора
Стоит использовать классы вместо функций конструктора — так значительно понятнее и наследование проще реализовывается.
Синтаксис класса — это синтаксический сахар для синтаксиса функций конструктора.
Например, определяем класс Person
следующим образом:
class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Видим, что конструктор принимает name
и age
в качестве параметров и задаёт как значения переменных инстанса.
Во всём блеске это удобство проявляется при необходимости расширения класса. Мы можем создать класс Employee
, в котором есть переменные инстанса класса Person
и который расширяет его следующим образом:
class Employee extends Person {
constructor(name, age, employeeCode) {
super(name, age);
this.employeeCode = employeeCode;
}
}
Всё, что нужно сделать — вызвать конструктор суперкласса super
, затем задать переменные инстанса класса Employee
в constructor
.
Используйте геттеры и сеттеры
Геттеры и сеттеры хороши, когда нужно скрыть фактические данные, к которым осуществляется доступ. У нас могут быть вычисляемые свойства, что означает, что мы можем отделить реализацию этих свойств, чтобы при изменении реализации не пришлось беспокоиться об изменении кода, который их использует.
Сеттеры также позволяют производить проверку данных перед их установкой. Кроме того, в них легко добавить обработку ошибок и регистрацию.
С геттерами можно использовать ленивую загрузку свойств объекта.
Например, используем геттеры следующим образом:
class Rectangle {
constructor(length, width) {
this._length = length;
this._width = width;
}
get area() {
return this._length * this._width;
}
}
Как видим, геттер можно использовать для создания вычисляемого свойства для получения площади прямоугольника.
Используем его так:
let rectangle = new Rectangle(1, 2);
console.log(rectangle.area);
Добавим проверку в сеттер:
class Rectangle {
constructor(length, width) {
this._length = length;
this._width = width;
}
get area() {
return this._length * this._width;
}
get length() {
return this._length;
}
set length(length) {
if (length <= 0) {
throw new Error('Length must be bigger than 0');
}
this._length = length;
}
get width() {
return this._width;
} set width(width) {
if (width <= 0) {
throw new Error('Width must be bigger than 0');
}
this._width = width;
}
}
Заметьте, определить геттер было весьма хорошей идеей, потому что мы получили доступ к значениям свойств.
Сохраняем приватность
В классах JavaScript нет приватных переменных, поэтому нам нужно определять их в области видимости блока, чтобы они не были доступны публично с let
и const
.
Методы цепочкой
Методы цепочкой делают вызов группы функций менее многословным. Мы можем определять функцию, которая должна быть цепной, возвращая this
. Например, напишем:
class Person {
setName(name) {
this.name = name;
return this;
}
setAge(age) {
this.age = age;
return this;
}
}
Затем используем так:
const person = new Person().setName('Joe').setAge(10);
При регистрации person
получим:
{name: "Joe", age: 10}
Это распространённый шаблон, использующийся во многих библиотеках, например jQuery и Lodash.
Композиция вместо наследования
Стоит оставить наследование класса только для отношений “целое-часть”, где содержимое одного объекта является подмножеством содержимого некоторого более крупного объекта. Например, сотрудник — это человек.
В остальных случаях стоит использовать композицию. Например, если мы хотим сохранить адрес для Person
, мы можем создать класс под названием Address
и инстанцировать его в методах класса Person
и использовать его. Так как объект Address
имеет отношение к объекту Person
, в этом случае наследование нам не нужно. Например, напишем:
class Address {
constructor(streetName) {
this.streetName = streetName;
}
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
setAddress() {
const address = new Address('123 A St.');
}
}
Заключение
Синтаксис класса значительно проще для понимания, чем синтаксис функций конструктора, особенно если нужно наследование. Поэтому стоит как можно быстрее уходить от синтаксиса функций конструктора. Наследование классов стоит использовать только в отношениях “целое-часть”. В остальных случаях используем композицию.
Также стоит использовать геттеры и сеттеры для сохранения частной реализации членов. Мы можем использовать геттеры для вычисляемых свойств и сеттеры для запуска кода до установки значений. Например, можно запустить проверочный код до установки значения с помощью сеттера.
Читайте также:
- Дуэт Markdown и JavaScript (mdjs) - залог отличной документации
- Сравниваем различные способы выполнения HTTP-запросов в JavaScript
- Двоичное дерево поиска: вставка значения с использованием JavaScript
Перевод статьи John Au-Yeung: JavaScript Clean Code — Objects and Classes