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

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

  • Принцип единой ответственности
  • Открытые / закрытые принципы
  • Лисков Принцип подстановки
  • Принцип разделения интерфейса
  • Принцип инверсии зависимостей

В этой статье мы рассмотрим каждый из них и увидим, как их можно применить в программах на JavaScript.

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

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

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

Несвязанные концепции в одном классе также затрудняют понимание цели кода.

Например, мы можем написать что-то вроде следующего, чтобы следовать принципу единой ответственности:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
}

Класс Rectangle выше имеет только length и width прямоугольника в качестве членов и позволяет нам получить из него площадь.

Он больше ничего не делает, поэтому следует принципу единой ответственности.

Плохой пример:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
  createCircle() {
  }
}

У нас должен быть createCircle метод в Rectangle классе, поскольку они не связаны между собой.

Принцип открытости / закрытости

Принцип открытости / закрытости гласит, что часть программного обеспечения открыта для расширения, но закрыта для модификации.

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

Например, если у нас есть следующий класс Rectangle:

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
}

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

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
  get perimteter() {
    return 2 * (this.length + this.width);
  }
}

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

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

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

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

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

Мы можем написать следующее, чтобы реализовать некоторые классы фигур и получить площадь каждого экземпляра:

class Shape {
  get area() {
    return 0;
  }
}
class Rectangle extends Shape {
  constructor(length, width) {
    super();
    this.length = length;
    this.width = width;
  }
  get area() {
    return this.length * this.width;
  }
}
class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }
  get area() {
    return this.length ** 2;
  }
}
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  get area() {
    return Math.PI * (this.radius ** 2);
  }
}
const shapes = [
  new Rectangle(1, 2),
  new Square(1, 2),
  new Circle(2),
]
for (let s of shapes) {
  console.log(s.area);
}

Поскольку мы переопределяем геттер area в каждом классе, расширяющем Shape, мы получаем правильную область для каждой формы, поскольку для каждой формы выполняется правильный код, чтобы получить область.

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

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

Это означает, что мы не должны навязывать реализацию чего-либо, если в этом нет необходимости.

У JavaScript нет интерфейсов, поэтому этот принцип не применяется напрямую, поскольку он не требует реализации чего-либо через интерфейсы.

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

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

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

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

По мере усложнения программного обеспечения, если мы не будем следовать этому принципу, наш код будет сильно ломаться.

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

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

Например, ниже представлена ​​простая реализация паттерна фасада:

class ClassA {
}
class ClassB {
}
class ClassC {
}
class Facade {
  constructor() {
    this.a = new ClassA();
    this.b = new ClassB();
    this.c = new ClassC();
  }
}
class Foo {
  constructor() {
    this.facade = new Facade();
  }
}

Нам не нужно беспокоиться о ClassA, ClassB и ClassC, чтобы реализовать класс Foo. Пока класс Facade не меняется, нам не нужно изменять наш собственный код.

Заключение

Мы должны следовать принципу SOLID, чтобы писать код, который легко поддерживать.

Чтобы следовать SOLID, мы должны написать классы, которые делают только одно.

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

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

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

Это позволяет уменьшить взаимосвязь между модулями.