Как использовать принцип SOLID в Javascript

Принципы SOLID можно применить к JavaScript следующими способами:

Принцип единой ответственности (SRP)

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

Пример функции с несколькими обязанностями

function processOrder(order) {
  // Validate the order
  if (!validateOrder(order)) {
    return false;
  }

  // Calculate the total cost of the order
  let totalCost = calculateTotalCost(order);

  // Charge the customer's credit card
  chargeCreditCard(order.customer, totalCost);

  // Send a confirmation email to the customer
  sendConfirmationEmail(order.customer);

  // Log the order in the system
  logOrder(order);

  return true;
}

Обновленная версия с одной ответственностью

// Refactored version with single responsibility
function validateOrder(order) {
  // Validate the order
  if (!validateOrder(order)) {
    return false;
  }
  return true;
}

function calculateTotalCost(order) {
    // Calculate the total cost of the order
    let totalCost = calculateTotalCost(order);
    return totalCost;
}

function chargeCreditCard(order, totalCost) {
    // Charge the customer's credit card
    chargeCreditCard(order.customer, totalCost);
}

function sendConfirmationEmail(order) {
    // Send a confirmation email to the customer
    sendConfirmationEmail(order.customer);
}

function logOrder(order) {
    // Log the order in the system
    logOrder(order);
}

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

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

Принцип открытия-закрытия (OCP)

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

Пример класса, который не открыт для расширения

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

Рефакторинговая версия, открытая для расширения

// Refactored version that is open for extension
class Shape {
  constructor() {}

  getArea() {
    throw new Error('This method must be implemented by a subclass.');
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

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

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

Принцип замещения Лисков (LSP)

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

Пример класса, нарушающего принцип подстановки Лисков

 
class Square {
  constructor(sideLength) {
    this.sideLength = sideLength;
  }

  setWidth(width) {
    this.sideLength = width;
  }

  setHeight(height) {
    this.sideLength = height;
  }

  getArea() {
    return this.sideLength * this.sideLength;
  }
}

Рефакторинговая версия, соответствующая принципу замещения Лисков

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

В этом примере показан класс с именем «Квадрат», который нарушает принцип подстановки Лискова. В классе есть методы setWidth и setHeight, которые используются для установки длины стороны квадрата. Это затрудняет использование класса в программе, которая ожидает прямоугольник, так как квадрат не может иметь разную ширину и высоту.

Рефакторинг версии класса — это класс «Прямоугольник», который имеет отдельные свойства ширины и высоты, придерживаясь принципа подстановки Лискова.

Принцип разделения интерфейсов (ISP)

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

Пример класса, нарушающего требования ISP

class Printer {
  print(document) {
    console.log(document);
  }

  scan() {
    // Scanning functionality
  }

  fax() {
    // Faxing functionality
  }
}

Обновленная версия, соответствующая принципу разделения интерфейса (ISP)

class Printer {
  print(document) {
    console.log(document);
  }
}

class Scanner {
  scan() {
    // Scanning functionality
  }
}

class FaxMachine {
  fax() {
    // Faxing functionality
  }
}

В этом примере показан класс с именем «Принтер», который нарушает правила ISP. Класс имеет три метода; печать, сканирование и факс. Это может затруднить использование и сопровождение класса, поскольку не всем клиентам класса потребуются все три метода.

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

Принцип инверсии зависимостей (DIP)

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

Пример класса, нарушающего DIP

class OrderService {
  constructor() {
    this.logger = new Logger();
  }

  processOrder(order) {
    // Process the order
    this.logger.log('Order processed.');
  }
}

Рефакторинговая версия, соответствующая принципу инверсии зависимостей (DIP)

class OrderService {
  constructor(logger) {
    this.logger = logger;
  }

  processOrder(order) {
    // Process the order
    this.logger.log('Order processed.');
  }
}

// Usage
const logger = new Logger();
const orderService = new OrderService(logger);
orderService.processOrder(someOrder);

В этом примере показан класс с именем «OrderService», который нарушает DIP. Класс имеет жестко запрограммированную зависимость от класса Logger, что затрудняет тестирование и поддержку класса.

Версия класса с рефакторингом использует внедрение зависимостей, чтобы принимать регистратор в качестве параметра конструктора, что делает класс более гибким и тестируемым. Класс «OrderService» теперь зависит от абстракции, а не от конкретной реализации. Это позволяет легко заменить регистратор на другую реализацию, не затрагивая остальную часть кода.

Заключение

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

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

Также вы можете связаться со мной через LinkedIn: https://www.linkedin.com/in/ankurpatel18/

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.