设计模式之六大设计原则

167 阅读9分钟

六大设计原则

评价代码的质量,一般都是从代码是否可维护,是否简单易读,可拓展性,灵活性,简洁性等方面来评价。设计模式是代码设计经验的总结,学习设计模式可以有效提高程序员的coding能力,让你写出质量上乘的代码。设计模式一共有23种,虽然数量不少,但是它们基本上都是这六大设计原则的实现方式。可以说,这六大设计原则就是23种设计模式的基本设计思想。本文简要介绍六种设计原则,并通过实际代码示例加以说明。 image.png

1. 单一职责原则(Single Responsibility Principle)

定义:一个类应该只有一个引起它变化的原因,即一个类只负责完成一个职责或者功能。

设计要点:避免设计大而全的类,要设计功能单一,粒度适中的类。

判断标准

  • 类中代码行数过多,代码功能职责不清晰;
  • 类中包含过多的函数和属性,导致代码维护困难;
  • 类的依赖过多,导致程序耦合度过高;
  • 类的私有方法过多且这些方法集中操作少数属性。

重构建议

当类变得庞大且功能不单一时,应该把它拆分成多个职责单一的小类。

代码示例

违反单一职责原则的类

// User类同时处理用户信息和用户数据存储
public class User {
    private String name;
    private String email;

    // 获取用户名称
    public String getName() {
        return name;
    }

    // 设置用户名称
    public void setName(String name) {
        this.name = name;
    }

    // 获取用户邮箱
    public String getEmail() {
        return email;
    }

    // 设置用户邮箱
    public void setEmail(String email) {
        this.email = email;
    }

    // 保存用户到数据库
    public void save() {
        // 代码实现保存用户到数据库
    }
}

遵循单一职责原则的类

// 仅负责用户信息的User类
public class User {
    private String name;
    private String email;

    // 获取用户名称
    public String getName() {
        return name;
    }

    // 设置用户名称
    public void setName(String name) {
        this.name = name;
    }

    // 获取用户邮箱
    public String getEmail() {
        return email;
    }

    // 设置用户邮箱
    public void setEmail(String email) {
        this.email = email;
    }
}

// 负责用户数据存储的UserRepository类
public class UserRepository {
    // 保存用户到数据库
    public void save(User user) {
        // 代码实现保存用户到数据库
    }
}

说明:通过将用户信息和数据存储职责分离,User类仅关注用户数据,而UserRepository类负责数据的持久化操作,提高了代码的可维护性和可扩展性。

2. 开闭原则(Open-Closed Principle)

定义:软件实体(类,模块,函数等)应对拓展开放,对修改封闭。

核心思想

  • 对拓展开放:允许通过拓展来增加新的功能。
  • 对修改封闭:在需求变化时,不通过直接修改已有功能代码,而是通过增加拓展模块来实现新的功能。

实现方法

使用抽象类或接口来定义稳定的结构,具体功能的拓展通过实现这些抽象类或接口来实现。

重要性

  • 符合开闭原则的软件可以更好地适应变化,维护成本更低;
  • 所有的设计模式都追求这一原则,以确保系统开发和维护的可靠性。

代码示例

违反开闭原则的类

public class GraphicEditor {
    public void drawShape(Shape shape) {
        if (shape.type == 1) {
            drawCircle((Circle) shape);
        } else if (shape.type == 2) {
            drawRectangle((Rectangle) shape);
        }
        // 如果新增形状,需要修改此方法
    }

    private void drawCircle(Circle circle) {
        // 绘制圆形的代码
    }

    private void drawRectangle(Rectangle rectangle) {
        // 绘制矩形的代码
    }
}

public class Shape {
    int type;
}

public class Circle extends Shape {
    public Circle() {
        type = 1;
    }
}

public class Rectangle extends Shape {
    public Rectangle() {
        type = 2;
    }
}

遵循开闭原则的类

// 抽象类Shape,定义绘制方法
public abstract class Shape {
    public abstract void draw();
}

// 圆形类
public class Circle extends Shape {
    @Override
    public void draw() {
        // 绘制圆形的代码
    }
}

// 矩形类
public class Rectangle extends Shape {
    @Override
    public void draw() {
        // 绘制矩形的代码
    }
}

// 图形编辑器类,不需要修改即可支持新形状
public class GraphicEditor {
    public void drawShape(Shape shape) {
        shape.draw();
    }
}

说明:通过使用抽象类ShapeGraphicEditor类无需修改即可支持新的形状,只需新增继承自Shape的类,实现draw方法即可。


3. 里氏替换原则(Liskov Substitution Principle)

定义:子类对象可以替换父类对象的任何地方,并保证程序的逻辑行为不变。

理解方式

  • 替换依赖于面向对象语言中的多态特性,子类对象应该可以在父类出现的任何地方替换父类对象。
  • 子类的实现应该遵守接口或父类定义的方法规范,即方法的输入和输出保持一致(保证行为不变)。

代码示例

违反里氏替换原则的类

// 父类鸟
public class Bird {
    public void fly() {
        // 鸟类飞行的实现
    }
}

// 企鹅类继承自鸟,但企鹅不会飞
public class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("企鹅不会飞");
    }
}

遵循里氏替换原则的类

// 抽象类动物
public abstract class Animal {
    // 一般动物的共同行为
}

// 可以飞行的动物接口
public interface Flyable {
    void fly();
}

// 鸟类实现Flyable接口
public class Bird extends Animal implements Flyable {
    @Override
    public void fly() {
        // 鸟类飞行的实现
    }
}

// 企鹅类不实现Flyable接口
public class Penguin extends Animal {
    // 企鹅的特有行为
}

说明:通过将会飞的行为抽象为Flyable接口,企鹅类不再继承不适用的fly方法,避免了因子类行为不一致导致的问题,符合里氏替换原则。


4. 接口隔离原则(Interface Segregation Principle)

定义:为各个类设计它们需要的专用接口,而不是设计一个庞大的通用接口供所有的类调用。

设计要点

  • 避免创建“胖接口”,而是要根据具体需求将接口拆分成对应的多个专一的接口。
  • 提高内聚性,降低耦合性,使类与接口之间的依赖关系更清晰。

与单一职责原则的区别:单一职责原则注重类的职责单一,接口隔离原则侧重于接口依赖的隔离。

代码示例

违反接口隔离原则的接口

public interface Worker {
    void work();
    void eat();
}

public class HumanWorker implements Worker {
    @Override
    public void work() {
        // 人类工作实现
    }

    @Override
    public void eat() {
        // 人类吃饭实现
    }
}

public class RobotWorker implements Worker {
    @Override
    public void work() {
        // 机器人工作实现
    }

    @Override
    public void eat() {
        // 机器人不需要吃饭,但必须实现该方法
        throw new UnsupportedOperationException("机器人不需要吃饭");
    }
}

遵循接口隔离原则的接口

// 工作接口
public interface Workable {
    void work();
}

// 吃饭接口
public interface Eatable {
    void eat();
}

public class HumanWorker implements Workable, Eatable {
    @Override
    public void work() {
        // 人类工作实现
    }

    @Override
    public void eat() {
        // 人类吃饭实现
    }
}

public class RobotWorker implements Workable {
    @Override
    public void work() {
        // 机器人工作实现
    }
}

说明:通过将Worker接口拆分为WorkableEatable,机器人类不再需要实现与其不相关的eat方法,符合接口隔离原则。


5. 依赖倒置原则(Dependency Inversion Principle)

定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖抽象。

实现方式

  • 面向接口编程,而不是面向具体实现。
  • 通过依赖注入的方式,将低层模块的实现注入到高层模块的抽象中。

目的

降低模块之间的耦合度,提高系统的灵活性和可维护性。

代码示例

违反依赖倒置原则的类

// 低层模块:数据库日志记录
public class DatabaseLogger {
    public void log(String message) {
        // 将日志记录到数据库
    }
}

// 高层模块:用户服务
public class UserService {
    private DatabaseLogger logger = new DatabaseLogger();

    public void createUser(String username) {
        // 创建用户的逻辑
        logger.log("User created: " + username);
    }
}

遵循依赖倒置原则的类

// 抽象日志记录接口
public interface Logger {
    void log(String message);
}

// 低层模块:数据库日志记录实现
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        // 将日志记录到数据库
    }
}

// 低层模块:文件日志记录实现
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 将日志记录到文件
    }
}

// 高层模块:用户服务
public class UserService {
    private Logger logger;

    // 通过构造函数注入依赖
    public UserService(Logger logger) {
        this.logger = logger;
    }

    public void createUser(String username) {
        // 创建用户的逻辑
        logger.log("User created: " + username);
    }
}

使用示例

public class Main {
    public static void main(String[] args) {
        // 选择具体的日志记录实现
        Logger logger = new DatabaseLogger();
        // 或者使用文件日志记录
        // Logger logger = new FileLogger();

        // 通过依赖注入将Logger传递给UserService
        UserService userService = new UserService(logger);
        userService.createUser("JohnDoe");
    }
}

说明:高层模块UserService依赖于抽象Logger接口,而不是具体的DatabaseLoggerFileLogger,从而实现了依赖倒置,增强了系统的灵活性和可扩展性。

6. 迪米特法则(Law of Demeter)

定义:一个类应该尽量少地依赖其他的类,减少类之间的相互依赖关系。

设计要点

  • 避免直接与不必要的类进行通信,减少类之间的直接依赖关系。
  • 降低类之间的耦合度,提高模块的独立性和可维护性。

代码示例

违反迪米特法则的类

public class OrderService {
    private OrderRepository orderRepository = new OrderRepository();

    public void processOrder(int orderId) {
        Order order = orderRepository.getOrderById(orderId);
        Customer customer = order.getCustomer();
        Address address = customer.getAddress();
        // 直接访问Customer和Address的细节
        sendEmail(address.getEmail(), "Your order has been processed.");
    }

    private void sendEmail(String email, String message) {
        // 发送邮件的逻辑
    }
}

遵循迪米特法则的类

// Customer类增加发送邮件的方法
public class Customer {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void sendEmail(String message) {
        // 发送邮件的逻辑,内部获取地址信息
        String email = address.getEmail();
        // 发送邮件
    }
}

// Order类增加获取Customer的方法
public class Order {
    private Customer customer;

    public Customer getCustomer() {
        return customer;
    }
}

// OrderRepository类
public class OrderRepository {
    public Order getOrderById(int orderId) {
        // 获取订单的逻辑
        return new Order();
    }
}

// OrderService类遵循迪米特法则
public class OrderService {
    private OrderRepository orderRepository = new OrderRepository();

    public void processOrder(int orderId) {
        Order order = orderRepository.getOrderById(orderId);
        // 通过Order直接调用Customer的方法,而不需要了解Customer的内部结构
        order.getCustomer().sendEmail("Your order has been processed.");
    }
}

说明:通过将发送邮件的职责委托给Customer类,OrderService类不再需要了解CustomerAddress的内部结构,减少了类之间的耦合,符合迪米特法则。


掌握这些设计原则,并在实际编码中灵活运用,将为后续学习23种设计模式打下坚实的基础,帮助你编写出高质量、可维护、可扩展的代码。