title: 架构师学习-SOLID原则 author: Gamehu date: 2025-12-12 22:43:25 tags:
在软件开发的早期,我们往往更关注功能的实现,而忽视了代码的设计质量。随着项目规模的增长,糟糕的代码设计会变得越来越难以维护,每一次修改都可能引发意想不到的问题。SOLID原则正是为了解决这些问题而诞生的。
SOLID原则能够帮助我们:
一个类应该只有一个引起它变化的原因,换句话说,一个类应该只有一个职责。
想象一个瑞士军刀,它有太多功能:刀、剪刀、开瓶器等。虽然功能强大,但当你只需要用刀的时候,带着整个军刀就显得笨重了。同样,一个类承担太多职责时,任何职责的变化都可能影响其他职责,导致系统变得脆弱。
/**
* 违反单一职责原则的用户服务类
* 该类同时负责用户业务逻辑和日志记录
*/
public class UserService {
// 职责1:用户管理
public void register(String username, String password) {
// 注册逻辑
System.out.println("用户注册:" + username);
}
public void login(String username, String password) {
// 登录逻辑
System.out.println("用户登录:" + username);
}
// 职责2:日志记录 - 这不应该属于用户服务
public void logToFile(String message) {
try {
FileWriter writer = new FileWriter("app.log", true);
writer.write(message + "\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 职责3:邮件发送 - 这也不应该属于用户服务
public void sendEmail(String to, String subject, String content) {
System.out.println("发送邮件给:" + to);
System.out.println("主题:" + subject);
System.out.println("内容:" + content);
}
}
/**
* 符合单一职责原则的用户服务类
* 只负责用户相关的业务逻辑
*/
public class UserService {
private Logger logger;
private EmailService emailService;
public UserService(Logger logger, EmailService emailService) {
this.logger = logger;
this.emailService = emailService;
}
// 只关注用户管理职责
public void register(String username, String password) {
logger.log("用户注册:" + username);
// 注册逻辑
emailService.sendEmail(username, "注册成功", "欢迎注册");
}
public void login(String username, String password) {
logger.log("用户登录:" + username);
// 登录逻辑
}
}
/**
* 专门的日志服务类
*/
public class Logger {
public void log(String message) {
try {
FileWriter writer = new FileWriter("app.log", true);
writer.write(message + "\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 专门的邮件服务类
*/
public class EmailService {
public void sendEmail(String to, String subject, String content) {
System.out.println("发送邮件给:" + to);
System.out.println("主题:" + subject);
System.out.println("内容:" + content);
}
}
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
这意味着当我们需要添加新功能时,应该通过扩展现有代码来实现,而不是修改已有的代码。
想象一个插线板,它有多个插座。当你需要使用新电器时,你只需要插上新的插头,而不需要拆开插线板重新布线。开闭原则就是让我们的代码像插线板一样,能够轻松"插入"新功能而不需要修改核心代码。
/**
* 违反开闭原则的折扣计算器
* 每次新增折扣类型都需要修改这个类
*/
public class DiscountCalculator {
public double calculateDiscount(String discountType, double price) {
switch (discountType) {
case "NONE":
return price;
case "TEN_PERCENT":
return price * 0.9;
case "TWENTY_PERCENT":
return price * 0.8;
// 每次新增折扣类型都需要在这里添加新的case
// 这违反了开闭原则
default:
return price;
}
}
}
/**
* 折扣策略接口
*/
public interface DiscountStrategy {
double calculate(double price);
}
/**
* 无折扣策略
*/
public class NoDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price;
}
}
/**
* 10%折扣策略
*/
public class TenPercentDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price * 0.9;
}
}
/**
* 20%折扣策略
*/
public class TwentyPercentDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price * 0.8;
}
}
/**
* 符合开闭原则的折扣计算器
*/
public class DiscountCalculator {
private DiscountStrategy discountStrategy;
public DiscountCalculator(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculate(double price) {
return discountStrategy.calculate(price);
}
// 可以动态切换折扣策略
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
DiscountCalculator calculator = new DiscountCalculator(new NoDiscount());
System.out.println("无折扣价格:" + calculator.calculate(100));
// 切换到10%折扣
calculator.setDiscountStrategy(new TenPercentDiscount());
System.out.println("10%折扣后价格:" + calculator.calculate(100));
// 切换到20%折扣
calculator.setDiscountStrategy(new TwentyPercentDiscount());
System.out.println("20%折扣后价格:" + calculator.calculate(100));
// 添加新的折扣类型只需创建新的策略类,无需修改DiscountCalculator
calculator.setDiscountStrategy(new ThirtyPercentDiscount());
System.out.println("30%折扣后价格:" + calculator.calculate(100));
}
}
/**
* 新增30%折扣策略 - 无需修改任何现有代码
*/
public class ThirtyPercentDiscount implements DiscountStrategy {
@Override
public double calculate(double price) {
return price * 0.7;
}
}
所有引用基类的地方必须能够透明地使用其子类的对象,子类可以替换父类出现在父类能够出现的任何地方,而不破坏程序的正确性。
如果你有一个正方形和一个长方形,从几何上讲,正方形是特殊的长方形。但在编程中,如果让正方形继承长方形类,可能会出现问题。因为正方形的长宽必须相等,这违反了长方形"长宽可以不同"的基本约定。
/**
* 长方形类
*/
public class Rectangle {
protected double width;
protected double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public void setWidth(double width) {
this.width = width;
}
public void setHeight(double height) {
this.height = height;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
public double getArea() {
return width * height;
}
}
/**
* 正方形类继承长方形
* 这违反了里氏替换原则
*/
public class Square extends Rectangle {
public Square(double size) {
super(size, size);
}
@Override
public void setWidth(double width) {
this.width = width;
this.height = width; // 正方形长宽必须相等
}
@Override
public void setHeight(double height) {
this.width = height; // 正方形长宽必须相等
this.height = height;
}
}
/**
* 测试类
*/
public class LSPTest {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(5, 10);
System.out.println("长方形面积:" + rectangle.getArea()); // 50
rectangle.setWidth(10);
rectangle.setHeight(5);
System.out.println("长方形面积:" + rectangle.getArea()); // 50
// 使用正方形替换长方形
Rectangle square = new Square(5);
System.out.println("正方形面积:" + square.getArea()); // 25
square.setWidth(10);
square.setHeight(5);
System.out.println("正方形面积:" + square.getArea()); // 25, 而不是50!
// 这里违反了里氏替换原则
// 当使用Square替换Rectangle时,程序的行为发生了变化
}
}
/**
* 形状接口
*/
public interface Shape {
double getArea();
}
/**
* 长方形类实现形状接口
*/
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public void setWidth(double width) {
this.width = width;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
/**
* 正方形类实现形状接口
*/
public class Square implements Shape {
private double size;
public Square(double size) {
this.size = size;
}
public void setSize(double size) {
this.size = size;
}
@Override
public double getArea() {
return size * size;
}
}
/**
* 形状工具类 - 可以处理任何实现Shape接口的对象
*/
public class ShapeUtils {
public static void printArea(Shape shape) {
System.out.println("形状面积:" + shape.getArea());
}
}
/**
* 测试类
*/
public class LSPTestCorrect {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(5, 10);
Square square = new Square(5);
// Rectangle和Square可以透明替换
ShapeUtils.printArea(rectangle); // 形状面积:50.0
ShapeUtils.printArea(square); // 形状面积:25.0
// 使用多态
List<Shape> shapes = Arrays.asList(rectangle, square);
for (Shape shape : shapes) {
ShapeUtils.printArea(shape);
}
}
}
客户端不应该被迫依赖于它不使用的接口,接口应该被拆分为更小和更具体的部分,这样客户端只需要知道它们所需的部分。
想象一个万能遥控器,上面有电视、空调、音响等各种设备的按钮。当你只需要控制电视时,面对这么多无关的按钮会很困扰。接口隔离原则就是让每个接口都专注于特定的功能,避免"胖接口"。
/**
* 臃肿的机器接口 - 包含所有可能的操作
*/
public interface Machine {
void print(String document);
void fax(String document);
void scan(String document);
void photocopy(String document);
}
/**
* 打印机类 - 被迫实现不需要的方法
*/
public class OldPrinter implements Machine {
@Override
public void print(String document) {
System.out.println("打印:" + document);
}
@Override
public void fax(String document) {
// 老式打印机不支持传真,但被迫实现这个方法
throw new UnsupportedOperationException("不支持传真功能");
}
@Override
public void scan(String document) {
// 老式打印机不支持扫描,但被迫实现这个方法
throw new UnsupportedOperationException("不支持扫描功能");
}
@Override
public void photocopy(String document) {
// 老式打印机不支持复印,但被迫实现这个方法
throw new UnsupportedOperationException("不支持复印功能");
}
}
/**
* 打印接口
*/
public interface Printer {
void print(String document);
}
/**
* 传真接口
*/
public interface Fax {
void fax(String document);
}
/**
* 扫描接口
*/
public interface Scanner {
void scan(String document);
}
/**
* 复印接口
*/
public interface Photocopier {
void photocopy(String document);
}
/**
* 简单打印机 - 只实现打印功能
*/
public class SimplePrinter implements Printer {
@Override
public void print(String document) {
System.out.println("打印:" + document);
}
}
/**
* 多功能打印机 - 实现多个接口
*/
public class MultiFunctionPrinter implements Printer, Fax, Scanner, Photocopier {
@Override
public void print(String document) {
System.out.println("打印:" + document);
}
@Override
public void fax(String document) {
System.out.println("传真:" + document);
}
@Override
public void scan(String document) {
System.out.println("扫描:" + document);
}
@Override
public void photocopy(String document) {
System.out.println("复印:" + document);
}
}
/**
* 打印扫描一体机 - 只实现需要的接口
*/
public class PrintScanCombo implements Printer, Scanner {
@Override
public void print(String document) {
System.out.println("打印:" + document);
}
@Override
public void scan(String document) {
System.out.println("扫描:" + document);
}
}
// 使用示例
public class ISPTest {
public static void main(String[] args) {
// 简单打印机只需要Printer接口
Printer simplePrinter = new SimplePrinter();
simplePrinter.print("简单文档");
// 多功能打印机可以实现所有功能
MultiFunctionPrinter mfp = new MultiFunctionPrinter();
usePrinter(mfp);
useFax(mfp);
useScanner(mfp);
// 打印扫描一体机只需要打印和扫描功能
PrintScanCombo combo = new PrintScanCombo();
usePrinter(combo);
useScanner(combo);
}
// 客户端只需要依赖它需要的接口
public static void usePrinter(Printer printer) {
printer.print("使用打印机");
}
public static void useFax(Fax fax) {
fax.fax("使用传真机");
}
public static void useScanner(Scanner scanner) {
scanner.scan("使用扫描仪");
}
}
高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
想象一个公司组织架构,总经理(高层)不应该直接管理员工(低层)的每一个具体工作。相反,总经理应该制定标准和接口,员工按照这些标准工作。这样,更换员工不会影响公司的整体运作。
/**
* MySQL数据库连接类 - 低层模块
*/
public class MySQLConnection {
public void connect() {
System.out.println("连接到MySQL数据库");
}
public void executeQuery(String sql) {
System.out.println("在MySQL中执行查询:" + sql);
}
}
/**
* 用户服务类 - 高层模块
* 直接依赖具体的MySQLConnection
*/
public class UserService {
private MySQLConnection dbConnection;
public UserService() {
this.dbConnection = new MySQLConnection(); // 高层依赖低层具体实现
}
public void getUser(String userId) {
dbConnection.connect();
dbConnection.executeQuery("SELECT * FROM users WHERE id = " + userId);
}
}
/**
* 数据库连接接口 - 抽象层
*/
public interface DatabaseConnection {
void connect();
void executeQuery(String sql);
}
/**
* MySQL数据库连接实现
*/
public class MySQLConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("连接到MySQL数据库");
}
@Override
public void executeQuery(String sql) {
System.out.println("在MySQL中执行查询:" + sql);
}
}
/**
* PostgreSQL数据库连接实现
*/
public class PostgreSQLConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("连接到PostgreSQL数据库");
}
@Override
public void executeQuery(String sql) {
System.out.println("在PostgreSQL中执行查询:" + sql);
}
}
/**
* MongoDB数据库连接实现
*/
public class MongoDBConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("连接到MongoDB数据库");
}
@Override
public void executeQuery(String sql) {
System.out.println("在MongoDB中执行查询:" + sql);
}
}
/**
* 符合依赖倒置原则的用户服务类
* 依赖抽象接口,而不是具体实现
*/
public class UserService {
private DatabaseConnection dbConnection;
// 通过构造器注入依赖
public UserService(DatabaseConnection dbConnection) {
this.dbConnection = dbConnection; // 依赖抽象而非具体实现
}
public void getUser(String userId) {
dbConnection.connect();
dbConnection.executeQuery("SELECT * FROM users WHERE id = " + userId);
}
// 也可以通过setter注入
public void setDbConnection(DatabaseConnection dbConnection) {
this.dbConnection = dbConnection;
}
}
// 使用示例
public class DIPTest {
public static void main(String[] args) {
// 可以轻松切换不同的数据库实现
DatabaseConnection mysqlConnection = new MySQLConnection();
DatabaseConnection pgConnection = new PostgreSQLConnection();
DatabaseConnection mongoConnection = new MongoDBConnection();
UserService userService1 = new UserService(mysqlConnection);
userService1.getUser("1");
UserService userService2 = new UserService(pgConnection);
userService2.getUser("2");
UserService userService3 = new UserService(mongoConnection);
userService3.getUser("3");
// 运行时切换数据库实现
userService1.setDbConnection(mongoConnection);
userService1.getUser("4");
}
}
┌─────────────────────────────────────────────────┐
│ SOLID 原则 │
├─────────────────────────────────────────────────┤
│ │
│ SRP ──────── 单一职责 ───────── 一个类只有一个职责 │
│ ↓ │
│ OCP ──────── 开闭原则 ────── 对扩展开放,对修改封闭 │
│ ↓ │
│ LSP ──────── 里氏替换 ──── 子类可以透明替换父类 │
│ ↓ │
│ ISP ──────── 接口隔离 ──── 接口应该小而专注 │
│ ↓ │
│ DIP ──────── 依赖倒置 ── 依赖抽象而非具体实现 │
│ │
└─────────────────────────────────────────────────┘
如何在项目中应用SOLID原则:
架构师学习系列 敬请期待下一篇