设计模式六大原则 — 列举反例详解各个原则的核心思想和意义

@TOC

概述

设计模式的六大原则是面向对象设计的基石,遵循这些原则可以提升代码的可维护性、可扩展性和可读性。下面对六大原则进行详细解析。

一、单一职责原则 (Single Responsibility Principle, SRP)

1. 定义

一个类应该只有一个引起变化的原因,即:一个类只负责一项职责

2. 核心思想

  • 高内聚:类的功能要集中
  • 低耦合:类之间的依赖要少

3. 代码示例

❌ 违反 SRP 的例子

java 复制代码
// 违反单一职责原则
public class UserService {
    public void registerUser(String username, String password) {
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密码长度不能少于6位");
        }
        
        saveUserToDatabase(username, password);
        sendWelcomeEmail(username);
    }
    
    private void saveUserToDatabase(String username, String password) {
        System.out.println("用户保存到数据库: " + username);
    }
    
    private void sendWelcomeEmail(String username) {
        System.out.println("发送欢迎邮件给: " + username);
    }
}

问题:UserService 同时承担了验证、持久化、邮件发送三种职责,职责不单一。

✅ 遵循 SRP 的改进

java 复制代码
public class User {
    private String username;
    private String password;
    // getter/setter
}

// 只负责验证
public class UserValidator {
    public void validateUser(User user) {
        if (user.getUsername() == null || user.getUsername().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (user.getPassword() == null || user.getPassword().length() < 6) {
            throw new IllegalArgumentException("密码长度不能少于6位");
        }
    }
}

// 只负责数据持久化
public class UserRepository {
    public void saveUser(User user) {
        System.out.println("用户保存到数据库: " + user.getUsername());
    }
}

// 只负责邮件发送
public class EmailService {
    public void sendWelcomeEmail(String username) {
        System.out.println("发送欢迎邮件给: " + username);
    }
}

// 协调各服务
public class UserService {
    private UserValidator validator;
    private UserRepository repository;
    private EmailService emailService;
    
    public UserService() {
        this.validator = new UserValidator();
        this.repository = new UserRepository();
        this.emailService = new EmailService();
    }
    
    public void registerUser(User user) {
        validator.validateUser(user);
        repository.saveUser(user);
        emailService.sendWelcomeEmail(user.getUsername());
    }
}

4. 优点

  • 降低类的复杂度
  • 提高类的可读性
  • 提高系统的可维护性
  • 降低变更引起的风险

二、开闭原则 (Open-Closed Principle, OCP)

1. 定义

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭

2. 核心思想

  • 通过抽象和接口实现扩展性
  • 新增功能时不需要修改现有代码

3. 代码示例

❌ 违反 OCP 的例子

java 复制代码
public class ShapeCalculator {
    public double calculateArea(String shapeType, double... params) {
        if ("circle".equals(shapeType)) {
            return Math.PI * params[0] * params[0];
        } else if ("rectangle".equals(shapeType)) {
            return params[0] * params[1];
        } else if ("triangle".equals(shapeType)) {
            return params[0] * params[1] / 2;
        }
        throw new IllegalArgumentException("不支持的形状类型");
    }
}

问题:每新增一种图形,都需要修改 calculateArea 方法,违反了"对修改关闭"。

✅ 遵循 OCP 的改进

java 复制代码
public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    @Override public double calculateArea() {
        return width * height;
    }
}

public class Triangle implements Shape {
    private double base, height;
    public Triangle(double base, double height) {
        this.base = base; this.height = height;
    }
    @Override public double calculateArea() {
        return base * height / 2;
    }
}

// 扩展无需修改
public class Ellipse implements Shape {
    private double a, b;
    public Ellipse(double a, double b) { this.a = a; this.b = b; }
    @Override public double calculateArea() {
        return Math.PI * a * b;
    }
}

// 面积计算器:对扩展开放,对修改关闭
public class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        return shapes.stream().mapToDouble(Shape::calculateArea).sum();
    }
}

4. 优点

  • 提高代码的可扩展性
  • 使软件更易于维护
  • 提高代码的稳定性

三、里氏替换原则 (Liskov Substitution Principle, LSP)

1. 定义

所有引用基类的地方必须能透明地使用其子类的对象

2. 核心思想

  • 子类可以扩展父类功能,但不能改变原有功能
  • 子类不应重写父类的非抽象方法,破坏行为一致性

3. 代码示例

❌ 违反 LSP 的例子

java 复制代码
class Rectangle {
    protected double width, height;
    public void setWidth(double width) { this.width = width; }
    public void setHeight(double height) { this.height = height; }
    public double getArea() { return width * height; }
}

class Square extends Rectangle {
    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setHeight(width); // 强制同步
    }
    
    @Override
    public void setHeight(double height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        Rectangle rect = new Square();
        rect.setWidth(5);
        rect.setHeight(10);
        System.out.println("面积: " + rect.getArea()); // 输出 100,期望 50
    }
}

问题:子类改变了父类的行为,导致多态失效。

✅ 遵循 LSP 的改进

java 复制代码
abstract class Shape {
    public abstract double getArea();
}

class Rectangle extends Shape {
    private double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    @Override public double getArea() { return width * height; }
}

class Square extends Shape {
    private double side;
    public Square(double side) { this.side = side; }
    @Override public double getArea() { return side * side; }
}

// 使用工厂创建
class ShapeFactory {
    public static Shape createRectangle(double w, double h) {
        return new Rectangle(w, h);
    }
    public static Shape createSquare(double s) {
        return new Square(s);
    }
}

4. 优点

  • 保证继承关系的正确性
  • 提高代码健壮性
  • 增强程序可靠性

四、接口隔离原则 (Interface Segregation Principle, ISP)

1. 定义

客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应建立在最小接口上

2. 核心思想

  • 接口要小而专,避免"胖接口"
  • 为不同客户端提供专用接口

3. 代码示例

❌ 违反 ISP 的例子

java 复制代码
interface Animal {
    void eat();
    void sleep();
    void fly();   // 并非所有动物都会飞
    void swim();  // 并非所有动物都会游泳
}

class Bird implements Animal {
    public void eat() { System.out.println("鸟在吃东西"); }
    public void sleep() { System.out.println("鸟在睡觉"); }
    public void fly() { System.out.println("鸟在飞翔"); }
    public void swim() { throw new UnsupportedOperationException("鸟不会游泳"); }
}

class Fish implements Animal {
    public void eat() { System.out.println("鱼在吃东西"); }
    public void sleep() { System.out.println("鱼在睡觉"); }
    public void fly() { throw new UnsupportedOperationException("鱼不会飞"); }
    public void swim() { System.out.println("鱼在游泳"); }
}

问题:实现类被迫实现无意义的方法。

✅ 遵循 ISP 的改进

java 复制代码
interface BasicAnimal { void eat(); void sleep(); }
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
interface Runnable { void run(); }

class Bird implements BasicAnimal, Flyable {
    public void eat() { System.out.println("鸟在吃东西"); }
    public void sleep() { System.out.println("鸟在睡觉"); }
    public void fly() { System.out.println("鸟在飞翔"); }
}

class Fish implements BasicAnimal, Swimmable {
    public void eat() { System.out.println("鱼在吃东西"); }
    public void sleep() { System.out.println("鱼在睡觉"); }
    public void swim() { System.out.println("鱼在游泳"); }
}

class Duck implements BasicAnimal, Flyable, Swimmable, Runnable {
    public void eat() { System.out.println("鸭子在吃东西"); }
    public void sleep() { System.out.println("鸭子在睡觉"); }
    public void fly() { System.out.println("鸭子在飞翔"); }
    public void swim() { System.out.println("鸭子在游泳"); }
    public void run() { System.out.println("鸭子在奔跑"); }
}

4.优点

  • 降低接口复杂度
  • 提高系统灵活性
  • 保证接口纯洁性

五、依赖倒置原则 (Dependency Inversion Principle, DIP)

1. 定义

  • 高层模块不应依赖低层模块,二者都应依赖抽象
  • 抽象不应依赖细节,细节应依赖抽象

2. 核心思想

  • 面向接口编程,而非面向实现
  • 使用依赖注入实现解耦

3. 代码示例

❌ 违反 DIP 的例子

java 复制代码
class MySQLDatabase {
    public void connect() { System.out.println("连接MySQL数据库"); }
    public void query(String sql) { System.out.println("执行MySQL查询: " + sql); }
}

class UserService {
    private MySQLDatabase database = new MySQLDatabase(); // 紧耦合
    
    public void getUserById(int id) {
        database.connect();
        database.query("SELECT * FROM users WHERE id = " + id);
    }
}

问题:UserService 直接依赖具体实现,难以替换数据库。

✅ 遵循 DIP 的改进

java 复制代码
interface Database {
    void connect();
    void query(String sql);
}

class MySQLDatabase implements Database {
    @Override public void connect() { System.out.println("连接MySQL数据库"); }
    @Override public void query(String sql) { System.out.println("执行MySQL查询: " + sql); }
}

class PostgreSQLDatabase implements Database {
    @Override public void connect() { System.out.println("连接PostgreSQL数据库"); }
    @Override public void query(String sql) { System.out.println("执行PostgreSQL查询: " + sql); }
}

class UserService {
    private Database database;
    
    public UserService(Database database) { // 依赖注入
        this.database = database;
    }
    
    public void getUserById(int id) {
        database.connect();
        database.query("SELECT * FROM users WHERE id = " + id);
    }
}

// 使用示例
public class DIPExample {
    public static void main(String[] args) {
        Database mysql = new MySQLDatabase();
        Database postgresql = new PostgreSQLDatabase();
        
        UserService userService1 = new UserService(mysql);
        userService1.getUserById(1);
        
        UserService userService2 = new UserService(postgresql);
        userService2.getUserById(1);
    }
}

4. 优点

  • 降低类间耦合度
  • 提高系统稳定性
  • 增强可维护性和可测试性

六、迪米特法则 (Law of Demeter, LoD) / 最少知识原则

1. 定义

一个对象应该对其他对象保持最少的了解,只与直接朋友通信

2. 核心思想

  • 降低类之间的耦合
  • 提高模块独立性

2. 代码示例

❌ 违反迪米特法则的例子

java 复制代码
class Company {
    private Employee manager;
    public Employee getManager() { return manager; }
}

class Employee {
    private Department department;
    public Department getDepartment() { return department; }
}

class Department {
    private String address;
    public String getAddress() { return address; }
}

class Client {
    public void printCompanyAddress(Company company) {
        // 违反:链式调用,了解过多内部结构
        System.out.println(company.getManager().getDepartment().getAddress());
    }
}

问题:客户端需了解 CompanyEmployeeDepartment 的链式结构。

✅ 遵循迪米特法则的改进

java 复制代码
class Company {
    private Employee manager;
    public String getCompanyAddress() {
        return manager.getDepartmentAddress(); // 隐藏细节
    }
}

class Employee {
    private Department department;
    public String getDepartmentAddress() {
        return department.getAddress();
    }
}

class Department {
    private String address;
    public String getAddress() { return address; }
}

class Client {
    public void printCompanyAddress(Company company) {
        // 只与 Company 通信
        System.out.println(company.getCompanyAddress());
    }
}

更完整的例子:电脑启动

java 复制代码
class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDisk hardDisk;
    
    public Computer(CPU cpu, Memory memory, HardDisk hardDisk) {
        this.cpu = cpu;
        this.memory = memory;
        this.hardDisk = hardDisk;
    }
    
    public void start() {
        cpu.start();
        memory.check();
        hardDisk.read();
        System.out.println("电脑启动成功");
    }
    
    public void shutdown() {
        cpu.shutdown();
        memory.clear();
        hardDisk.write();
        System.out.println("电脑关闭成功");
    }
}

// 用户只需调用 Computer,无需了解内部组件
class User {
    public void useComputer() {
        Computer computer = new Computer(new CPU(), new Memory(), new HardDisk());
        computer.start();
        computer.shutdown();
    }
}

4. 优点

  • 降低类之间的耦合度
  • 提高模块的相对独立性
  • 提高系统的可维护性

七、六大原则总结

原则 核心思想 关键点
单一职责原则 (SRP) 一个类只负责一项职责 高内聚、低耦合
开闭原则 (OCP) 对扩展开放,对修改关闭 抽象、多态、接口
里氏替换原则 (LSP) 子类可替换父类 继承的正确使用
接口隔离原则 (ISP) 使用多个专门的接口 接口细化、避免冗余
依赖倒置原则 (DIP) 依赖抽象而非实现 面向接口编程、依赖注入
迪米特法则 (LoD) 最少知识原则 降低耦合、封装细节

八、综合应用示例

java 复制代码
// 1. 单一职责 + 接口隔离
interface Payment { void pay(double amount); }
interface Refund { void refund(double amount); }

// 2. 开闭原则 + 依赖倒置
class PaymentProcessor {
    private Payment payment;
    public PaymentProcessor(Payment payment) {
        this.payment = payment;
    }
    public void processPayment(double amount) {
        payment.pay(amount);
    }
}

// 3. 里氏替换原则
class CreditCardPayment implements Payment, Refund {
    public void pay(double amount) { System.out.println("信用卡支付: " + amount); }
    public void refund(double amount) { System.out.println("信用卡退款: " + amount); }
}

class PayPalPayment implements Payment {
    public void pay(double amount) { System.out.println("PayPal支付: " + amount); }
}

// 4. 迪米特法则
class OrderService {
    private PaymentProcessor paymentProcessor;
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    public void checkout(double amount) {
        paymentProcessor.processPayment(amount); // 客户端无需知道细节
    }
}

总结

这六大原则是构建高质量、可维护、可扩展软件系统的基础。在实际开发中,应灵活运用这些原则,避免过度设计,做到"恰到好处的抽象"。


💡 提示:原则是指导,不是教条。结合业务场景合理使用才是关键。

相关推荐
用户30745969820713 小时前
门面(Facade)—— 静态语法的“动态伪装术”
后端·php
辜月十13 小时前
CentOS7 离线安装字体
后端
superman超哥14 小时前
仓颉语言中流式I/O的设计模式深度剖析
开发语言·后端·设计模式·仓颉
豆浆whisky14 小时前
Go内存管理最佳实践:提升性能的Do‘s与Don‘ts|Go语言进阶(17)
开发语言·后端·golang
Kay_Liang14 小时前
Spring中@Controller与@RestController核心解析
java·开发语言·spring boot·后端·spring·mvc·注解
weixin_4978455414 小时前
Windows系统Rust安装慢的问题
开发语言·后端·rust
IT_陈寒15 小时前
React性能优化:10个90%开发者不知道的useEffect正确使用姿势
前端·人工智能·后端
Apifox15 小时前
如何在 Apifox 中使用 OpenAPI 的 discriminator?
前端·后端·测试
yuuki23323315 小时前
【数据结构】双向链表的实现
c语言·数据结构·后端
朝新_15 小时前
【SpringBoot】玩转 Spring Boot 日志:级别划分、持久化、格式配置及 Lombok 简化使用
java·spring boot·笔记·后端·spring·javaee