likes
comments
collection
share

你了解SOLID设计原则吗?

作者站长头像
站长
· 阅读数 29

随着软件系统变得越来越复杂,编写可维护、可扩展和可测试的代码变得越来越重要。为了保持代码的高质量,创建开发速度更快、更易于更改且不易出错的代码至关重要,因此Robert C. Martin(Uncle Bob)提出的 SOLID 原则是一套可以帮助您实现代码可维护、可扩展和可测试的代码。这些原则旨在使您的代码更灵活、模块化,并且更易于理解和维护。在本文中,我们将通过代码示例探索每个 SOLID 原则,便于帮助您更好地理解它们。

1.单一职责原则(SRP)

单一职责原则规定,一个类应该只有一个改变的原因。换句话说,一个类应该只有一个职责或工作。通过遵守这一原则,您可以创建更易于理解、维护和测试的类。

代码示例

假设我们有一个UserManager类,负责处理用户注册、密码重置和发送通知。该类违反了 SRP,因为它具有多项职责。

public class UserManager {
    public void registerUser(String email, String password) {
        // Register user logic
    }

    public void resetPassword(String email) {
        // Reset password logic
    }

    public void sendNotification(String email, String message) {
        // Send notification logic
    }
}

为了遵守 SRP,我们可以将职责划分为不同的类别:

public class UserRegistration {
    public void registerUser(String email, String password) {
        // Register user logic
    }
}

public class PasswordReset {
    public void resetPassword(String email) {
        // Reset password logic
    }
}

public class NotificationService {
    public void sendNotification(String email, String message) {
        // Send notification logic
    }
}

2.开放/封闭原则(OCP)

开放/封闭原则规定软件实体(类、模块、函数等)应该对扩展开放,但对修改封闭。简而言之,您应该能够在不修改现有代码的情况下添加新功能。

代码示例

让我们考虑一个ShapeCalculator计算不同形状面积的类。如果不遵守 OCP,我们在添加对新形状的支持时可能会得到很多条件语句。

public class ShapeCalculator {
    public double calculateArea(Shape shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        }
        // More conditional statements for other shapes
        return 0;
    }
}

为了遵守 OCP,我们可以引入一个抽象类并在每个具体形状类中Shape定义方法。calculateArea

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

public class Circle extends Shape {
    private double radius;

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

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

public class Rectangle extends Shape {
    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;
    }
}

我们可以添加新的形状而不必修改ShapeCalculator类。

3.里氏替换原则(LSP)

里氏替换原则规定子类型必须可以替换其基类型。换句话说,如果您有一个基类和一个派生类,那么您应该能够在需要基类实例的任何地方使用派生类的实例,而不会影响程序的正确性。

代码示例

让我们考虑一个Vehicle类和一个Car扩展的类Vehicle。根据 LSP,任何与Vehicle对象一起工作的代码也应该与Car对象正确地一起工作。

public class Vehicle {
    public void startEngine() {
        // Start engine logic
    }
}

public class Car extends Vehicle {
    @Override
    public void startEngine() {
        // Additional logic for starting a car engine
        super.startEngine();
    }
}

在此示例中,Car类重写了startEngine方法并通过添加其他逻辑来扩展行为。但是,如果我们以意外的方式更改行为并违反 LSP,则可能会导致问题。

public class Car extends Vehicle {
    @Override
    public void startEngine() {
        // Different behavior, e.g., throwing an exception
        throw new RuntimeException("Engine cannot be started");
    }
}

这里,Car类违反了 LSP,抛出了异常而不是启动引擎。这意味着,预期与对象一起工作的代码可能会在对象被替换Vehicle时中断。Car

4.接口隔离原则(ISP)

接口隔离原则规定,不应强迫客户端依赖他们不使用的接口。换句话说,最好拥有许多较小、集中的接口,而不是一个庞大、单片的接口。

代码示例

假设我们有一个Worker接口,它为不同类型的工人(全职、兼职、承包商)定义方法。

public interface Worker {
    void calculateFullTimeSalary();
    void calculatePartTimeSalary();
    void calculateContractorHours();
}

public class FullTimeEmployee implements Worker {
    @Override
    public void calculateFullTimeSalary() {
        // Calculate full-time salary
    }

    @Override
    public void calculatePartTimeSalary() {
        // Not applicable, throw exception or leave empty
    }

    @Override
    public void calculateContractorHours() {
        // Not applicable, throw exception or leave empty
    }
}

在此示例中,FullTimeEmployee该类被迫实现它不需要的方法,违反了 ISP。为了解决这个问题,我们可以将接口划分为更小、更集中的接口:

public interface FullTimeWorker {
    void calculateFullTimeSalary();
}

public interface PartTimeWorker {
    void calculatePartTimeSalary();
}

public interface ContractWorker {
    void calculateContractorHours();
}

public class FullTimeEmployee implements FullTimeWorker {
    @Override
    public void calculateFullTimeSalary() {
        // Calculate full-time salary
    }
}

5.依赖倒置原则(DIP)

依赖倒置原则指出,高级模块不应该依赖于低级模块;两者都应该依赖于抽象。此外,抽象不应该依赖于细节;细节应该依赖于抽象。

代码示例

让我们考虑一个UserService依赖于另一个DatabaseRepository类进行数据访问的类。

public class UserService {
    private DatabaseRepository databaseRepository;

    public UserService() {
        databaseRepository = new DatabaseRepository();
    }

    public void createUser(String email, String password) {
        // Use databaseRepository to create a new user
    }
}

在这个例子中,UserService类与类紧密耦合DatabaseRepository,违反了 DIP。为了遵守 DIP,我们可以引入一个抽象(接口),并在运行时注入实现。

public interface Repository {
    void createUser(String email, String password);
}

public class DatabaseRepository implements Repository {
    @Override
    public void createUser(String email, String password) {
        // Database logic to create a new user
    }
}

public class UserService {
    private Repository repository;

    public UserService(Repository repository) {
        this.repository = repository;
    }

    public void createUser(String email, String password) {
        repository.createUser(email, password);
    }
}

现在,UserService类依赖于Repository抽象,而实现(DatabaseRepository)可以在运行时注入。这使得代码更加模块化、可测试且更易于维护。

通过遵循 SOLID 原则,您可以创建更易于维护、扩展和测试的代码。这些原则促进了模块化设计、松散耦合和关注点分离,最终提高了软件质量并简化了维护。

转载自:https://juejin.cn/post/7385904495384264740
评论
请登录