При разработке программного обеспечения поддержка, расширение и гибкость кода важны для долгосрочного успеха проекта. Принципы SOLID были сформулированы, чтобы помочь разработчикам создавать код, который легче понять, модифицировать и расширять. В этой статье мы поговорим о каждом из пяти принципов SOLID и о том, как их использовать на практических примерах на Java.
Принцип единой ответственности (SRP) устанавливает, что у класса должна быть только одна причина для изменения, то есть он должен нести единственную ответственность в системе.
// Antes de aplicar o SRP class ProductService { public void saveProduct(Product product) { // Lógica para salvar o produto no banco de dados } public void sendEmail(Product product) { // Lógica para enviar um email sobre o produto } }
// Após aplicar o SRP class ProductService { public void saveProduct(Product product) { // Lógica para salvar o produto no banco de dados } } class EmailService { public void sendEmail(Product product) { // Lógica para enviar um email sobre o produto } }
В примере мы отделяем ответственность за сохранение продукта в базе данных от ответственности за отправку писем о продукте. Это облегчает будущие изменения, поскольку изменения в отправке электронных писем больше не влияют на логику сохранения продукта.
Принцип открытости/закрытости (OCP) предполагает, что программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации. Это достигается за счет использования абстракций и наследования.
// Exemplo inicial violando o OCP class AreaCalculator { public double calculateArea(Rectangle[] rectangles) { double area = 0; for (Rectangle rectangle : rectangles) { area = rectangle.width * rectangle.height; } return area; } }
// Exemplo após aplicar o OCP interface Forma { double calculateArea(); } class Rectangle implements Forma { private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double calculateArea() { return width * height; } } class AreaCalculator { public double calculateArea(Forma [] formas) { double area = 0; for (Forma formas: formas) { area = forma.calculateArea(); } return area; } }
Во втором примере изначально класс AreaCalculator напрямую зависел от класса Rectangle. Это означает, что если вы захотите добавить фигуру другого типа, например, круг или треугольник, вам придется изменить класс AreaCalculator, нарушив тем самым OCP. Благодаря созданию интерфейса Shape класс AreaCalculator получил возможность получать новые геометрические фигуры без изменения существующего кода.
Принцип замены Лискова (LSP) гласит, что объекты суперкласса должны быть заменяемы объектами его подклассов, не затрагивая при этом целостность системы. Другими словами, поведение подклассов должно быть согласовано с поведением суперклассов.
// Classe base class Bird { public void fly() { // Método padrão que imprime "Flying" System.out.println("Flying"); } } // Classe derivada que viola o LSP class Duck extends Bird { @Override public void fly() { // Sobrescrita que imprime "Ducks cannot fly" System.out.println("Ducks cannot fly"); } }
Проблема: класс Duck переопределяет метод fly() для печати «Утки не могут летать», тем самым изменяя поведение по умолчанию, определенное в базовом классе Bird, а именно: все птицы летают («Летают»). Это нарушает LSP, поскольку любой код, который ожидает, что объект Bird или его подклассы будут летать, не будет корректно работать с Duck, который, как мы уже знаем, не летает.
// Classe derivada que respeita o LSP interface Bird { void fly(); } class Eagle implements Bird { @Override public void fly() { System.out.println("Flying like an Eagle"); } } class Duck implements Bird { @Override public void fly() { throw new UnsupportedOperationException("Ducks cannot fly"); } }
Благодаря такому подходу Eagle и Duck могут быть взаимозаменяемыми там, где ожидается Bird, не нарушая при этом ожиданий, установленных интерфейсом Bird. Исключение, выдаваемое Duck, явно сообщает, что утки не летают, не изменяя поведение суперкласса таким образом, чтобы это могло вызвать непредвиденные проблемы в коде.
Принцип разделения интерфейсов (ISP) предполагает, что интерфейсы класса должны быть специфичными для клиентов, которые их используют. Это позволяет избежать «толстых» интерфейсов, требующих реализации методов, не используемых клиентами.
// Exemplo antes de aplicar o ISP interface Worker { void work(); void eat(); void sleep(); } class Programmer implements Worker { @Override public void work() { // Lógica específica para programar } @Override public void eat() { // Lógica para comer } @Override public void sleep() { // Lógica para dormir } }
// Exemplo após aplicar o ISP interface Worker { void work(); } interface Eater { void eat(); } interface Sleeper { void sleep(); } class Programmer implements Worker, Eater, Sleeper { @Override public void work() { // Lógica específica para programar } @Override public void eat() { // Lógica para comer } @Override public void sleep() { // Lógica para dormir } }
В этом примере мы разделили интерфейс Worker на более мелкие интерфейсы (Work, Eat, Sleep), чтобы гарантировать, что классы, которые их реализуют, имеют только те методы, которые им нужны. Это избавляет классы от необходимости реализовывать методы, которые к ним не относятся, улучшая ясность и связность кода.
Принцип инверсии зависимостей (DIP) предполагает, что модули высокого уровня (такие как бизнес-классы или классы приложений, которые реализуют основные бизнес-правила) не должны зависеть от модулей низкого уровня (классы инфраструктуры, такие как доступ к внешним данным и сервисы, поддерживающие операции высокого уровня). Оба должны зависеть от абстракций.
// Exemplo antes de aplicar o DIP class BackendDeveloper { public void writeJava() { // Lógica para escrever em Java } } class Project { private BackendDeveloper developer; public Project() { this.developer = new BackendDeveloper(); } public void implement() { developer.writeJava(); } }
// Exemplo após aplicar o DIP interface Developer { void develop(); } class BackendDeveloper implements Developer { @Override public void develop() { // Lógica para escrever em Java } } class Project { private Developer developer; public Project(Developer developer) { this.developer = developer; } public void implement() { developer.develop(); } }
Класс Project теперь зависит от абстракции (Developer), а не от конкретной реализации (BackendDeveloper). Это позволяет легко внедрять различные типы разработчиков (например, FrontendDeveloper, MobileDeveloper) в класс Project без изменения его кода.
Принятие принципов SOLID не только повышает качество вашего кода, но также укрепляет ваши технические навыки, повышает эффективность вашей работы и ускоряет ваш карьерный путь в качестве разработчика программного обеспечения.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3