Введение

Принципы проектирования SOLID необходимы для написания поддерживаемого, масштабируемого и надежного кода. Эти принципы были представлены Робертом С. Мартином, также известным как «дядя Боб», и получили широкое признание в сообществе разработчиков программного обеспечения. В этой подробной статье мы рассмотрим принципы проектирования SOLID в контексте JavaScript, предоставив параллельные примеры кода, пояснения и руководство по реализации. В конце вы найдете удобную памятку, которая поможет вам начать применять принципы SOLID в своих проектах. Давайте погрузимся!

Оглавление:

  1. Принцип единой ответственности (SRP)
  2. Открытый/закрытый принцип (OCP)
  3. Принцип замещения Лисков (LSP)
  4. Принцип разделения интерфейсов (ISP)
  5. Принцип инверсии зависимостей (DIP)
  6. Шпаргалка SOLID
  7. Заключение
  8. Принцип единой ответственности (SRP)

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

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

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

class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }

    save() {
        // Code to save user data to the database
    }

    sendEmail(message) {
        // Code to send an email to the user
    }
}

Хорошо Пример:

class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
}

class UserRepository {
    save(user) {
        // Code to save user data to the database
    }
}

class EmailService {
    sendEmail(user, message) {
        // Code to send an email to the user
    }
}

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

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

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

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

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

class AreaCalculator {
    calculateArea(rectangle) {
        return rectangle.width * rectangle.height;
    }
}

Хороший пример:

class Shape {
    area() {
        throw new Error("Not implemented");
    }
}

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

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

class AreaCalculator {
    calculateArea(shape) {
        return shape.area();
    }
}

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

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

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

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

class Bird {
    fly() {
        // Implementation
    }
}

class Penguin extends Bird {
    fly() {
        throw new Error("Penguins can't fly");
    }
}

Хорошо Пример:

class Bird {
    // ...
}

class FlyingBird extends Bird {
    fly() {
        // Implementation
    }
}

class NonFlyingBird extends Bird {
    // ...
}

class Penguin extends NonFlyingBird {
    // ...
}

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

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

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

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

class SmartDevice {
    powerOn() {
        // Implementation
    }

    powerOff() {
        // Implementation
    }

    connectToWifi() {
        // Implementation
    }

    makePhoneCall() {
        // Implementation
    }
}

Хороший пример

class PowerControl {
    powerOn() {
        // Implementation
    }

    powerOff() {
        // Implementation
    }
}

class WifiControl {
    connectToWifi() {
        // Implementation
    }
}

class PhoneControl {
    makePhoneCall() {
        // Implementation
    }
}

class SmartDevice {
    constructor() {
        this.powerControl = new PowerControl();
        this.wifiControl = new WifiControl();
        this.phoneControl = new PhoneControl();
    }
}

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

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

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

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

class Database {
    saveData(data) {
        // Implementation
    }
}

class UserManager {
    constructor() {
        this.database = new Database();
    }

    saveUser(user) {
        this.database.saveData(user);
    }
}

Хороший пример:

class Database {
    saveData(data) {
        throw new Error("Not implemented");
    }
}

class MySQLDatabase extends Database {
    saveData(data) {
        // Implementation
    }
}

class UserManager {
    constructor(database) {
        this.database = database;
    }

    saveUser(user) {
        this.database.saveData(user);
    }
}

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

ТВЕРДЫЙ. Шпаргалка

  1. Принцип единой ответственности (SRP) — у класса должна быть только одна обязанность.
  2. Принцип открытости/закрытости (OCP) — класс должен быть открыт для расширения, но закрыт для модификации.
  3. Принцип замещения Лискова (LSP) — объекты суперкласса должны заменяться объектами подкласса без ущерба для корректности программы.
  4. Принцип разделения интерфейсов (ISP) — клиенты не должны зависеть от интерфейсов, которые они не используют.
  5. Принцип инверсии зависимостей (DIP) — модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

Заключение

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