Принципы SOLID – это набор из пяти принципов проектирования, помогающих создавать удобные в сопровождении и гибкие программные системы. Эти принципы были придуманы Робертом С. Мартином (также известным как дядя Боб) и широко используются в объектно-ориентированном программировании, включая Java. Аббревиатура SOLID означает следующие принципы:

  1. Принцип единой ответственности (SRP). У класса должна быть только одна причина для изменения. Это означает, что класс должен иметь только одну обязанность или должен сосредоточиться на выполнении одной задачи. Придерживаясь этого принципа, вы гарантируете, что у класса будет меньше причин для изменения, что упростит его понимание, поддержку и тестирование.
// Before
class User {
    public void authenticate(String username, String password) {
        // Authentication logic
    }

    public void updateProfile(User user) {
        // Update profile logic
    }
}

// After
class AuthenticationService {
    public void authenticate(String username, String password) {
        // Authentication logic
    }
}

class UserProfileService {
    public void updateProfile(User user) {
        // Update profile logic
    }
}

2. Принцип открытого-закрытого (OCP): объекты программного обеспечения (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации. Этот принцип предполагает, что вы должны проектировать свои классы и модули таким образом, чтобы их можно было легко расширять новыми функциями без изменения существующего кода. Это способствует повторному использованию кода и упрощает внедрение новых функций.

abstract class Shape {
    public abstract double calculateArea();
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double calculateArea() {
        return width * height;
    }
}

// Adding a new shape (Square) without modifying existing code
class Square extends Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    public double calculateArea() {
        return side * side;
    }
}

3. Принцип подстановки Лескова (LSP): объекты суперкласса должны быть заменяемы объектами его подклассов, не влияя на корректность программы. Другими словами, подклассы должны иметь возможность заменять свои базовые (супер) классы, не вызывая неожиданного поведения или нарушения контрактов базового класса. Этот принцип гарантирует, что поведение базового класса сохраняется в его подклассах, и помогает в разработке иерархий классов.

interface IFlyable {
    void fly();
}

class Bird implements IFlyable {
    public void fly() {
        System.out.println("Bird is flying...");
    }
}

class Penguin {
    // Penguins cannot fly, so they don't implement IFlyable
    public void swim() {
        System.out.println("Penguin is swimming...");
    }
}

// Usage of LSP
IFlyable bird = new Bird();
bird.fly();

Penguin penguin = new Penguin();
penguin.swim();

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

interface Printable {
    void print();
}

interface Scannable {
    void scan();
}

interface Faxable {
    void fax();
}

class AllInOnePrinter implements Printable, Scannable, Faxable {
    public void print() {
        System.out.println("Printing...");
    }

    public void scan() {
        System.out.println("Scanning...");
    }

    public void fax() {
        System.out.println("Faxing...");
    }
}

class BasicPrinter implements Printable {
    public void print() {
        System.out.println("Printing...");
    }
}

// Usage of ISP
Printable printer = new BasicPrinter();
printer.print();

5. Принцип инверсии зависимостей (DIP): Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Этот принцип подчеркивает важность использования абстракций (интерфейсов или абстрактных классов), а не конкретных реализаций. В зависимости от абстракций вы можете разделить модули и сделать их более гибкими и взаимозаменяемыми. Это способствует использованию контейнеров внедрения зависимостей и инверсии управления (IoC).

interface DataAccess {
    void fetchData();
}

class DatabaseAccess implements DataAccess {
    public void fetchData() {
        System.out.println("Fetching data from the database...");
    }
}

class BusinessService {
    private DataAccess dataAccess;

    public BusinessService(DataAccess dataAccess) {
        this.dataAccess = dataAccess;
    }

    public void doBusinessLogic() {
        // Use dataAccess to fetch data and perform business logic
        dataAccess.fetchData();
        System.out.println("Performing business logic...");
    }
}

// Usage of DIP
DataAccess dbAccess = new DatabaseAccess();
BusinessService service = new BusinessService(dbAccess);
service.doBusinessLogic();

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

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