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

@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());
    }
}

问题:客户端需了解 Company → Employee → Department 的链式结构。

✅ 遵循迪米特法则的改进

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); // 客户端无需知道细节
    }
}

总结 :

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


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

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
大勇前进3 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu3 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile3 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白803 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙3 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发3 天前
软件工程SOLID 五大设计原则
后端·软件工程