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

В этой статье мы рассмотрим, как организовать данные так, чтобы их было легко использовать. 

Используйте классы вместо функций конструктора

Стоит использовать классы вместо функций конструктора — так значительно понятнее и наследование проще реализовывается. 

Синтаксис класса — это синтаксический сахар для синтаксиса функций конструктора. 

Например, определяем класс 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.

Используйте геттеры и сеттеры 

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

Telegram-канал по теме
NOP::Nuances of ProgrammingСтатьи и переводы для разработчиков — каждый день
Подписаться

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

С геттерами можно использовать ленивую загрузку свойств объекта. 

Например, используем геттеры следующим образом:

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.');
  }
}

Заключение

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

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

Читайте также:


Перевод статьи John Au-Yeung: JavaScript Clean Code — Objects and Classes

Мы в Telegram — подписывайтесь
NOP::Nuances of ProgrammingСтатьи и переводы для разработчиков — каждый день
Подписаться
NOP::HumorИТ-юмор и мемы о боли разработчиков
Подписаться
NOP::Recruiter RemoteУдалённые ИТ-вакансии с вилками
Подписаться
Предыдущая статьяПрограммирование: 5 недооцененных навыков
Следующая статья3 случая, когда линейная модель может ошибаться