Оригинал: It’s time we talk about Object Oriented JavaScript, автор Aphinya Dechalert

Ветераны бэкенда исторически смотрели на JavaScript сверху вниз, как на «ненастоящий» язык. В основном потому, что начинался он с функций, что ограничивает его потенциальные возможности для роста и развития, и использовался лишь для придания html-страницам некоторой динамики, что, ясное дело, совсем не внушает доверия.

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

Практика объектно-ориентированного программирования — один из способов создания качественного и надежного кода. Далеко не все JavaScript-разработчики используют ее, но, возможно, это именно то, что вам нужно.

Что такое объектно-ориентированное программирование?

Идея ООП заключается в том, что вы создаете чертёж или план того, как должен выглядеть ваш «объект», а затем используете его снова и снова.

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

Например, объект Заказа (CustomerOrder) имеет методы для получения информации об этом заказе.

class CustomerOrder {
  constructor(customerId, orderId) {
    this.customerId = customerId;
    this.orderId = orderId;
  }

  // примеры методов
  
  // геттер свойства как точка входа 
  get orderDetails() {
    return this.pullOrderDetails();
  }

  // метод для сбора информации о заказе
  pullOrderDetails() {
    // этот код может обращаться к свойствам
    // которые были переданы в конструктор
    // при создании объекта
    // внедрение зависимостей не требуется
    return;
  }
}

// создание нового объекта CustomerOrder
const order_1 = new CustromerOrder(8273918, 'FA-39482x');

// получение информации о заказе без внедрения зависимостей
console.log(order_1.orderDetails);

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

const customerId = 8273918;
const orderId = 'FA-39482x';

function pullOrderDetails(customerId, orderId) {
  // код для получения информации о заказе
  return;
}

console.log(pullOrderDetails(8273918, 'FA-39482x'));

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

На что похожи классы
На что похожи классы

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

Каждый раз при создании нового объекта, весь класс с его методами как бы «клонируется». Любое изменение в классе будет единообразно внесено во все созданные объекты этого класса.

Начало спагетти-кода
Начало спагетти-кода

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

В объектно-ориентированном программировании у вас есть геттеры и сеттеры, а все, что за ними — черный ящик, о котором вам не требуется знать ничего.

Зачем нужно ООП в JavaScript?

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

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

Вот так начинается спагетти-код в реальной жизни:

function pullOrderDetails(customerId, orderId) {
  // код для сбора информации о заказе
  return orderDetails;
}

function pullCustomerDetails(customerId) {
  // код для сбора информации о заказчике
  return customerAddress;
}

function sendCustomerPizza(customerId, orderDetails, customerAddress) {
  // необходимый код
  return;
}

const customerId = 8273918;
const orderId = "FA-39482x";

ProcessOrder(customerId, orderId) {
  // сначала нужно получить детали заказа
  // возможно это происходит асинхронно, так что используем промисы
  pullOrderDetails(customerId, orderId)
  .then(function(value) {
    // вам нужно позаботиться о сохранении полученных данных
    // ведь они потребуются для функции sendCustomerPizza
    return pullCustomerDetails(customerId);
  }).then(function(value) {
    return sendCustomerPizza(customerId, orderDetails, customerAddress);
  }).then(function(value) {
    return 'success';
  });
}

// а теперь повторяем все это еще для трех других заказов

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

class CustomerOrder {
  constructor(customerId, orderId) {
    this.customerId = customerId;
    this.orderId = orderId;
  }

  // примеры методов

  // геттер как точка входа
  get orderDetails() {
    return this.pullOrderDetails();
  }

  get sendCustomerPizza() {
    return this.sendPizza();
  }

  pullOrderDetails() {
    // необходимый код
    return;
  }

  sendPizza() {
    // необходимый код
    return;
  }
}

// создание новых заказов
const order_1 = new CustomerOrder(8273918, "FA-39482x");
const order_2 = new CustomerOrder(1353372, "FX-394322x");

// отправка заказа и получение информации без инъекций зависимостей
console.log(order_1.sendCustomerPizza);
console.log(order_2.sendCustomerPizza);
console.log(order_2.orderDetails);

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

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

Заключение

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

Возможно, объектно-ориентированное программирование будет для вас более удобным и простым решением.

0 комментариев

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован