你了解SOLID设计原则吗?
随着软件系统变得越来越复杂,编写可维护、可扩展和可测试的代码变得越来越重要。为了保持代码的高质量,创建开发速度更快、更易于更改且不易出错的代码至关重要,因此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