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

概述

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

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

1. 定义

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

2. 核心思想

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

3. 代码示例

违反 SRP 的例子

// 违反单一职责原则
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 的改进

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 的例子

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 的改进

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 的例子

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 的改进

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 的例子

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 的改进

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 的例子

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 的改进

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. 代码示例

违反迪米特法则的例子

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 的链式结构。

遵循迪米特法则的改进

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

更完整的例子:电脑启动

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)最少知识原则降低耦合、封装细节

八、综合应用示例

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

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


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

评论 73
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木易 士心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值