Принципы SOLID в JavaScript с примерами

RedDeveloper
11.03.2023 13:20
Принципы SOLID в JavaScript с примерами

SOLID означает 5 из следующих  принципов

Принцип единой ответственности подразумевает то, что:

  • Для изменения модуля должна быть одна единственная причина

При нарушении принципа единой ответственности:

class ConcertLineups {
  constructor(maximumBandLimits) {
    this.lineups = [];
  }
  addBandsToLineup(bandName) {
    this.lineups.push(bandName);
  }
  displayLineups() {
    console.info(this.lineups);
  }
}
const concertLineups = new ConcertLineups();
concertLineups.addBandsToLineup('Warfaze');
concertLineups.addBandsToLineup('Karnival');
concertLineups.addBandsToLineup('SBC');
concertLineups.displayLineups();

Здесь у модуля ConcertLineups есть две причины для изменения:

  • Как мы добавляем составы
  • Как мы отображаем составы

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

Более эффективным является подход, использующий принципы единой ответственности:

class Logger {
  log(message) {
      console.info(message);
  }
}

class ConcertLineups {
  constructor(maximumBandLimits) {
    this.lineups = [];
    this.logger = new Logger();
  }
  addBandsToLineup(bandName) {
    this.lineups.push(bandName);
  }
  displayLineups() {
    this.logger.log(this.lineups);
  }
}
const concertLineups = new ConcertLineups();
concertLineups.addBandsToLineup('Warfaze');
concertLineups.addBandsToLineup('Karnival');
concertLineups.addBandsToLineup('SBC');
concertLineups.displayLineups();

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

Открытый закрытый принцип

  • Модуль должен быть открыт для расширения, но закрыт для модификации

Рассмотрим следующий код, если мы обновляем другой платеж, нам нужно добавить еще один случай переключения. Это модифицирует существующий класс PaymentProcessor.Open closed pr

class StripePayment {
  constructor(paymentType) {
    this.paymentType = paymentType;
  }
}

class PaypalPayment {
  constructor(paymentType) {
    this.paymentType = paymentType;
  }
}
class PaymentProcessor {
  constructor(paymentAdaptar) {
    this.paymentAdaptar = paymentAdaptar;
  }
  pay() {
    switch(this.paymentAdaptar.paymentType) {
      case 'Stripe':
        makeStripePayment();
        break;
      case 'Paypal':
        makePaypalPayment();
        break;
    }
  }
}
const makeStripePayment = () => {
  // make payment
}
const makePaypalPayment = () => {
  // make payment
}

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

class StripePayment {
  constructor(paymentType) {
    this.paymentType = paymentType;
  }
  
  makePayment () {
    // pay with stripe
  }
}

class PaypalPayment {
  constructor(paymentType) {
    this.paymentType = paymentType;
  }
  makePayment() {
    // pay with paypal
  }
}

class PaymentProcessor {
  constructor(paymentAdaptar) {
    this.paymentAdaptar = paymentAdaptar;
  }
  pay() {
    this.paymentAdaptar.makePayment();
  }
}

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

Принцип замещения Лискова подразумевает, что:

  • Объекты суперкласса должны вести себя как объекты подкласса, они должны быть заменяемыми

У нас есть суперкласс, Vehicle. Мы создали два подкласса Car и Cycle из Vehicle. У автомобиля есть метод startEngine. Но, похоже, что у Cycle нет двигателя.

Поэтому объект Vehicle и объект Cycle ведут себя не одинаково. Рассмотрим следующий пример:

class Vehicle {
  constructor(name) {
    this.name = name;
  }
  startEngine() {
    console.info(`${this.name} engine started`);
  }
}
class Car extends Vehicle {
  constructor(name) {
    super(name);
  }
}
class Cycle extends Vehicle {
  constructor(name) {
    super(name);
  }
  startEngine() {
    throw new Error(`${this.name} does not have an engine.`)
  }
}
const car = new Car('My Car');
car.startEngine();
const cycle = new Cycle('My Cycle');
cycle.startEngine();

Чтобы решить эту проблему, мы можем создать еще два подкласса после Vehicle, один MotorVehicle, который имеет двигатель, а другой ManualVehicle, который не имеет двигателя.

class Vehicle {
  constructor(name) {
    this.name = name;
  }
}
class MotorVehicle {
  constructor(name) {
    this.name = name;
  }
  startEngine() {
    console.info(`${this.name} engine started`);
  }
}
class ManualVehicle {
  constructor(name) {
    this.name = name;
  }
  startMoving() {
    console.info(`${this.name} started moving`);
  }
}
class Car extends MotorVehicle {
  constructor(name) {
    super(name);
  }
}
class Cycle extends ManualVehicle {
  constructor(name) {
    super(name);
  }
  startEngine() {
    throw new Error(`${this.name} does not have an engine.`)
  }
}
const car = new Car('My Car');
car.startEngine();
const cycle = new Cycle('My Cycle');
cycle.startMoving();

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

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

В следующем примере у нас есть интерфейс Shape, который реализован в классе Square и классе Cube. Мы можем вычислить объем для куба, но не для квадрата. Поэтому, когда мы реализуем Shape для класса Square, он выдает ошибку для метода volume.

interface Shape {
  area: () => void;
  volume: () => void;
}

class Square implements Shape {
  height: number;
  width: number;
  constructor(height: number) {
    this.height = height;
    this.width = height;
  }
  area () {
    console.info(this.height * this.width);
  }
  volume() {
    throw new Error('Volume can not valculated on 2d shape');
  }
}
class Cube implements Shape {
  height: number;
  width: number;
  length: number;
  constructor(height: number, width: number, length: number) {
    this.height = height;
    this.width = height;
    this.length = length;
  }
  area () {
    console.info(this.height * this.width);
  } 
  volume () {
    console.info(this.height * this.width * this.length);
  } 
}
const square = new Square(5);
square.area();
square.volume();
const cube = new Cube(5, 6, 7);
cube.area();
cube.volume();

L использовать при создании класса Cube.

interface Shape {
  area: () => void;
}

interface Shape3D extends Shape {
  volume: () => void;
}
class Square implements Shape {
  height: number;
  width: number;
  constructor(height: number) {
    this.height = height;
    this.width = height;
  }
  area () {
    console.info(this.height * this.width);
  }
}
class Cube implements Shape3D {
  height: number;
  width: number;
  length: number;
  constructor(height: number, width: number, length: number) {
    this.height = height;
    this.width = height;
    this.length = length;
  }
  area () {
    console.info(this.height * this.width);
  } 
  volume () {
    console.info(this.height * this.width * this.length);
  } 
}
const square = new Square(5);
square.area();
const cube = new Cube(5, 6, 7);
cube.area();
cube.volume();

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

Принцип инверсии зависимостей подразумевает,

  • Модули высокого уровня не должны зависеть от модулей низкого уровня, оба должны зависеть от абстракцийДля решения этой проблемы мы можем ввести расширенный интерфейс Shape3D. Этот новый интерфейс будет иметь метод объема и Wil
  • Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций

В следующем коде мы создаем класс CourseService в соответствии с CourseController. Здесь Courservice не должен зависеть от CourseController, вместо этого оба должны зависеть от интерфейса.

class CourseService {
  get() {
    console.info('All the courses');
  }
}

class CourseController {
  constructor(courseService) {
    this.courseService = courseService;
  }
  getAllCourse() {
    this.courseService.get();
  }
}
const courseService = new CourseService();
const courseController = new CourseController(courseService);
courseController.getAllCourse();

Чтобы решить эту проблему, нужен сервис, от которого может зависеть CourseService,

interface Service {
  get: () => void;
}

class CourseService implements Service {
  get() {
    console.info('All the courses');
  }
}
class CourseController {
  courseService: Service;
  constructor(courseService: Service) {
    this.courseService = courseService;
  }
  getAllCourse() {
    this.courseService.get();
  }
}
const courseService = new CourseService();
const courseController = new CourseController(courseService);
courseController.getAllCourse();

Lean Code: SOLID - Бо учит JavaScript

Ускорьте разработку веб-приложений Laravel с помощью этих бесплатных стартовых наборов
Ускорьте разработку веб-приложений Laravel с помощью этих бесплатных стартовых наборов

31.03.2023 11:40

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

Что такое двойные вопросительные знаки (??) в JavaScript?
Что такое двойные вопросительные знаки (??) в JavaScript?

31.03.2023 11:16

Как безопасно обрабатывать неопределенные и нулевые значения в коде с помощью Nullish Coalescing

Создание ресурсов API Laravel: Советы по производительности и масштабируемости
Создание ресурсов API Laravel: Советы по производительности и масштабируемости

31.03.2023 11:06

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

Как сделать компонент справочного центра с помощью TailwindCSS
Как сделать компонент справочного центра с помощью TailwindCSS

31.03.2023 10:15

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

Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript

30.03.2023 14:11

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

Как подобрать выигрышные акции с помощью анализа и визуализации на Python
Как подобрать выигрышные акции с помощью анализа и визуализации на Python

30.03.2023 13:54

Отказ от ответственности: Эта статья предназначена только для демонстрации и не должна использоваться в качестве инвестиционного совета.