Что такое ТВЕРДОЕ ТЕЛО?
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 Listdevelopers; 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”