В этой статье вы узнаете о принципах SOLID. Вы получите понимание каждого принципа вместе с примерами кода на языке Java.
Принципы SOLID – это набор из пяти дизайн-принципов, используемых в объектно-ориентированном программировании. Следование этим принципам поможет вам разработать прочное программное обеспечение. Эти принципы сделают ваш код более эффективным, читабельным и maintainable.
SOLID – это акроним, который означает:
- Принцип единственной ответственности
- Принцип открытости/закрытости
- Принцип Лiskov
- Принцип сгруппировки интерфейсов
- Принцип инверсии зависимости
Принцип единственной ответственности
Принцип единственной ответственности гласит, что каждая класс должна иметь единственную, концентрированную ответственность, единственную причину для изменения.
public class Employee{
public String getDesignation(int employeeID){ // }
public void updateSalary(int employeeID){ // }
public void sendMail(){ // }
}
В приведенном выше примере класс Employee
имеет несколько методов, специфичных для класса Employee, таких как getDesignation
и updateSalary
.
Кроме того, у него есть еще один метод с именем sendMail
, который отклоняется от обязанностей класса Employee
.
Это поведение не специфично для этого класса и нарушает принцип единственной ответственности. Чтобы этого избежать, вы можете переместить метод sendMail
в отдельный класс.
Вот как это можно сделать:
public class Employee{
public String getDesignation(int employeeID){ // }
public void updateSalary(int employeeID){ // }
}
public class NotificationService {
public void sendMail() { // }
}
Принцип открытости/закрытости
Согласно принципу открытости/закрытости, компоненты должны быть открыты для расширения, но закрыты для модификации. Чтобы понять этот принцип, рассмотрим пример класса, который вычисляет площадь фигуры.
public class AreaCalculator(){
public double area(Shape shape){
double areaOfShape;
if(shape instanceof Square){
// вычислить площадь квадрата
} else if(shape instanceof Circle){
// вычислить площадь круга
}
return areaOfShape;
}
Проблема с вышеуказанным примером заключается в том, что если в будущем появится новый экземпляр типа Shape
, для которого нужно вычислить площадь, вам придётся модифицировать вышеуказанный класс, добавив ещё один условий else-if
. Вы будете делать это для каждого нового объекта типа Shape
.
Чтобы этого устранить, можно создать интерфейс и сделать так, что каждый Shape
реализует этот интерфейс. Затем, каждая класс может предоставить свое собственное реализацию для вычисления площади. Это сделает ваше программное обеспечение легко расширяемым в будущем.
interface IAreaCalculator(){
double area();
}
class Square implements IAreaCalculator{
@Override
public double area(){
System.out.println("Calculating area for Square");
return 0.0;
}
}
class Circle implements IAreaCalculator{
@Override
public double area(){
System.out.println("Calculating area for Circle");
return 0.0;
}
}
Принцип замещения Лискова
Принцип замещения Лискова утверждает, что вы должны смогте заменить объект суперкласса объектом подкласса без влияния на корректность программы.
abstract class Bird{
abstract void fly();
}
class Eagle extends Bird {
@Override
public void fly() { // какая-то реализация }
}
class Ostrich extends Bird {
@Override
public void fly() { // имитация реализации }
}
В классе Eagle
и классе Ostrich
, оба из которых расширяют класс Bird
и переопределяют метод fly()
, происходит нарушение принципа Лисков.
Потому что Ostrich
классу приходится предоставлять пустую реализацию, так как он не может летать, и, следglich, он не ведет себя так же, если мы заменим объект класса Bird
на него.
abstract class FlyingBird{
abstract void fly();
}
abstract class NonFlyingBird{
abstract void doSomething();
}
class Eagle extends FlyingBird {
@Override
public void fly() { Чтобы решить эту проблему, мы можем создать отдельный класс для птиц, которые могут летать, и сделать так, чтобы Eagle
расширял его, в то время как другие птицы могут расширять другой класс, который не будет включать никакого поведения fly
.// некоторая реализация }
}
class Ostrich extends NonFlyingBird {
@Override
public void doSomething() { // некоторая реализация }
}
Принцип Сегregation Interface
Согласно принципу сегregation интерфейса, следует строить небольшие, сосредоточенные интерфейсы, которые не заставляют клиента реализовывать поведение, которое ему не нужно.
Простым примером был бы интерфейс, который вычисляет и площадь, и объем фигуры.
interface IShapeAreaCalculator(){
double calculateArea();
double calculateVolume();
}
class Square implements IShapeAreaCalculator{
double calculateArea(){ // вычисление площади }
double calculateVolume(){ // пустая реализация }
}
Проблема с этим заключается в том, что если Квадрат
форма реализует это, то он вынужден реализовывать метод calculateVolume()
, которого ему не нужно.
С другой стороны, Cube
может реализовать оба. Чтобы это преодолеть, мы можем разделить интерфейс и создать два различных интерфейса: один для вычисления площади и другой для вычисления объема. Это позволит отдельным фигурам решить, что им реализовать.
interface IAreaCalculator {
double calculateArea();
}
interface IVolumeCalculator {
double calculateVolume();
}
class Square implements IAreaCalculator {
@Override
public double calculateArea() { // calculate the area }
}
class Cube implements IAreaCalculator, IVolumeCalculator {
@Override
public double calculateArea() { // calculate the area }
@Override
public double calculateVolume() {// calculate the volume }
}
Принцип вversion Dependency
По принципу dependency inversion, высокоуровневые модули не должны зависеть от низкоуровневых модулей. Другими словами, вы должны следовать abstraction и обеспечить loose coupling
public interface Notification {
void notify();
}
public class EmailNotification implements Notification {
public void notify() {
System.out.println("Sending notification via email");
}
}
public class Employee {
private EmailNotification emailNotification;
public Employee(EmailNotification emailNotification) {
this.emailNotification = emailNotification;
}
public void notifyUser() {
emailNotification.notify();
}
}
В данном примере класс Employee
напрямую зависит от класса EmailNotification
, который является низкоуровневым модулем. Это нарушает принцип dependency inversion.
public interface Notification{
public void notify();
}
public class Employee{
private Notification notification;
public Employee(Notification notification){
this.notification = notification;
}
public void notifyUser(){
notification.notify();
}
}
public class EmailNotification implements Notification{
public void notify(){
//implement notification via email
}
}
public static void main(String [] args){
Notification notification = new EmailNotification();
Employee employee = new Employee(notification);
employee.notifyUser();
}
В приведенном выше примере мы обеспечили loose coupling. Employee
не зависит от какой-либо конкретной реализации, а только от абстракции (интерфейса уведомления).
Если我们需要 изменить способ уведомления, мы можем создать новую реализацию и передать ее в Employee
.
Заключение
В заключение, мы ознакомились с сутью SOLID принципов через простые примеры в этой статье.
Эти принципы являются основанием для разработки приложений с высокой extendibility и reusability.
Свяжитесь с мной в LinkedIn.
Source:
https://www.freecodecamp.org/news/introduction-to-solid-principles/