Рубрики
Без рубрики

Твердые принципы объектно-ориентированного проектирования

Что такое ТВЕРДОЕ ТЕЛО? SOLID помогает вам писать код, который легко поддерживать, расширять и понимать… С тегами java, ооп, архитектура.

Что такое ТВЕРДОЕ ТЕЛО?

SOLID помогает вам писать код, который легко поддерживать, расширять и понимать.

Это аббревиатура для следующих 5 принципов:

S = Single-responsibility principle
O = Open-closed principle
L = Liskov substitution principle
I = Interface segregation principle
D = Dependency inversion principle

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

  • Класс/модуль должен отвечать только за одно.
  • У класса/модуля должна быть только одна причина для изменения.

Вот пример, который нарушает этот принцип:

public class Customer {
    public void add(Database db) {
        try {
            db.execute("INSERT INTO...");
        } catch (Exception e) {
            File.writeAllText("/var/log/error.log", e.toString());
        }
    }
}

Класс Customer отвечает как за запись в базу данных, так и за запись в файл журнала.

  • Если мы хотим изменить способ регистрации ошибок, Клиент должен измениться.
  • Если мы хотим изменить способ записи в БД, Клиент должен измениться.

Этот код следует переработать, чтобы:

class Customer {
    private FileLogger logger = new FileLogger();
    void add(Database db) {
        try {
            db.execute("INSERT INTO...");
        } catch (Exception e) {
            logger.log(e.toString());
        }
    }
}
class FileLogger {
    void log(String error) {
        File.writeAllText("/var/log/error.log", error);
    }
}

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

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

Дальнейшее чтение:

Принцип “Открыто-закрыто”

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

Вот пример, который нарушает этот принцип:

public void pay(Request request) {
    Payment payment = new Payment();
    if (request.getType().eq("credit")) {
        payment.payWithCreditCard();
    } else {
        payment.payWithPaypal();
    }
}
public class Payment {
    public void payWithCreditCard() {
        // logic for paying with credit card
    }
    public void payWithPaypal() {
        // logic for paying with paypal
    }
}

Что, если бы мы захотели добавить новый способ оплаты? Нам пришлось бы изменить класс Оплата , что нарушает принцип “открыто-закрыто”.

Этот код следует переработать, чтобы:

public void pay(Request request) {
    PaymentFactory paymentFactory = new PaymentFactory();
    payment = paymentFactory.initialisePayment(request.getType());
    payment.pay();
}
//-----------------------
public class PaymentFactory {
    public Payment intialisePayment(String type) {
        if (type.eq("credit")) {
            return new CreditCardPayment();
        } elseif (type.eq("paypal")) {
            return new PaypalPayment();
        } elseif (type.eq("wire")) {
            return new WirePayment();
        }
        throw new Exception("Unsupported payment method");
    }
}
//-----------------------
interface Payment {
    public void pay();
}
class CreditCardPayment implements Payment {
    public void pay() {
        // logic for paying with credit card
    }
}
class PaypalPayment implements Payment {
    public void pay() {
        // logic for paying with paypal
    }
}
class WirePayment implements Payment {
    public void pay() {
        // logic for paying with wire
    }
}

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

Дальнейшее чтение:

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

  • Если класс реализует интерфейс, он должен иметь возможность заменять любую ссылку, реализующую тот же интерфейс.
  • например, если класс называется MySQL реализует База данных , и другой класс, называемый MongoDB реализует База данных , вы должны быть в состоянии заменить MySQL объекты для MongoDB объектов.

Вот пример, который нарушает этот принцип:

public abstract class Bird {
    public abstract void Fly();
}
public class Parrot : Bird {
    public override void Fly() {
        // logic for flying
    }
}
public class Ostrich : Bird {
    public override void Fly() {
         // Can't implement as ostriches can't fly
        throw new NotImplementedException();
    }
}

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

Затем этот код можно было бы преобразовать в:

public abstract class Bird {
}
public abstract class FlyingBird : Bird {
    public abstract void Fly();
}
public class Parrot : FlyingBird {
    public override void Fly() {
        // logic for flying
    }
}
public class Ostrich : Bird {
}

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

Вот еще одна, более реальная встреча с этим принципом:

  • У вас есть класс с именем Банковский счет с методом вывод средств() . Все ли банковские счета разрешают вывод средств? Например, счет с фиксированным депозитом не позволяет снимать средства.

Дальнейшее чтение:

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

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

Вот пример, который нарушает этот принцип:

public interface Athlete {
    void compete();
    void swim();
    void highJump();
    void longJump();
}
public class JohnDoe implements Athlete {
    @Override
    public void compete() {
        System.out.println("John Doe started competing");
    }
    @Override
    public void swim() {
        System.out.println("John Doe started swimming");
    }
    @Override
    public void highJump() {
    }
    @Override
    public void longJump() {
    }
}

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

Затем этот код можно было бы преобразовать в:

public interface Athlete {
    void compete();
}
public interface SwimmingAthlete extends Athlete {
    void swim();
}
public interface JumpingAthlete extends Athlete {
    void highJump();
    void longJump();
}
public class JohnDoe implements SwimmingAthlete {
    @Override
    public void compete() {
        System.out.println("John Doe started competing");
    }
    @Override
    public void swim() {
        System.out.println("John Doe started swimming");
    }
}

Теперь Неизвестный не должен выполнять действия, которые он не способен выполнить.

Дальнейшее чтение:

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

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

Вот пример, который нарушает этот принцип:

public class BackEndDeveloper {
    public void writeJava() {
    }
}
public class FrontEndDeveloper {
    public void writeJavascript() {
    }
}
public class Project {
    private BackEndDeveloper backEndDeveloper = new BackEndDeveloper();
    private FrontEndDeveloper frontEndDeveloper = new FrontEndDeveloper();
    public void implement() {
        backEndDeveloper.writeJava();
        frontEndDeveloper.writeJavascript();
    }
}

Класс Project является модулем высокого уровня, и он зависит от модулей низкого уровня, таких как Серверный разработчик и Разработчик внешнего интерфейса

Этот код следует переработать, чтобы:

public interface Developer {
    void develop();
}
public class BackEndDeveloper implements Developer {
    @Override
    public void develop() {
        writeJava();
    }
    private void writeJava() {
    }
}
public class FrontEndDeveloper implements Developer {
    @Override
    public void develop() {
        writeJavascript();
    }
    public void writeJavascript() {
    }
}
//-----------------------
public class Project {
    private List developers;
    public Project(List developers) {
        this.developers = developers;
    }
    public void implement() {
        developers.forEach(d -> d.develop());
    }
}

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

Дальнейшее чтение:

Заключительное примечание: Не будьте слишком строги с ТВЕРДЫМИ принципами

  • ТВЕРДЫЕ принципы проектирования – это принципы, а не правила.
  • Всегда используйте здравый смысл при применении SOLID (знайте свои компромиссы).
  • Обычно при использовании SOLID требуется больше времени на написание кода, поэтому вы можете потратить меньше времени на его чтение позже.
  • Наконец, не забывайте использовать SOLID как инструмент, а не как цель.

Оригинал: “https://dev.to/seymour7/the-solid-principles-of-object-oriented-design-53ho”