六大设计原则

简介: 本文介绍了六大设计原则,包括单一职责、开闭原则、里氏替换、接口隔离、依赖倒置和迪米特法则。每项原则均通过定义、设计要点及代码示例进行说明。单一职责强调类的功能应单一;开闭原则提倡对扩展开放、对修改封闭;里氏替换要求子类能无缝替换父类;接口隔离主张拆分专用接口;依赖倒置鼓励面向抽象编程;迪米特法则减少类间依赖。掌握这些原则有助于编写高质量、可维护的代码,并为学习23种设计模式奠定基础。

theme: v-green

六大设计原则

评价代码的质量,一般都是从代码是否可维护,是否简单易读,可拓展性,灵活性,简洁性等方面来评价。设计模式是代码设计经验的总结,学习设计模式可以有效提高程序员的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种设计模式打下坚实的基础,帮助你编写出高质量、可维护、可扩展的代码。

相关文章
|
SQL 关系型数据库 MySQL
MySQL唯一约束(UNIQUE KEY)
MySQL唯一约束(UNIQUE KEY)
1256 0
|
编译器 测试技术 开发工具
让你的 XCode 编译链接耗时减半
让你的 XCode 编译链接耗时减半
1954 0
让你的 XCode 编译链接耗时减半
|
存储 前端开发 Java
Element-UI中el-upload上传组件(demo详解)
案例详解Element-UI中el-upload上传组件,一起打卡学习吧!
2735 0
Element-UI中el-upload上传组件(demo详解)
|
Dart 开发工具 Android开发
如何验证Flutter环境配置是否成功?
如何验证Flutter环境配置是否成功?
1485 164
|
8月前
|
分布式计算 关系型数据库 Hadoop
一、Sqoop历史发展及原理
在大数据系统中,Sqoop 就像是一位干练的“数据搬运工”,帮助我们把 MySQL、Oracle 等数据库里的数据快速、安全地导入到 Hadoop、Hive 或 HDFS 中,反之亦然。这个专栏从基础原理讲起,配合实战案例、参数详解和踩坑提醒,让你逐步掌握 Sqoop 的使用技巧。不管你是初学者,还是正在构建数据管道的工程师,都能在这里找到实用的经验和灵感。
348 6
|
消息中间件 存储 Java
RocketMQ(一):消息中间件缘起,一览整体架构及核心组件
【10月更文挑战第15天】本文介绍了消息中间件的基本概念和特点,重点解析了RocketMQ的整体架构和核心组件。消息中间件如RocketMQ、RabbitMQ、Kafka等,具备异步通信、持久化、削峰填谷、系统解耦等特点,适用于分布式系统。RocketMQ的架构包括NameServer、Broker、Producer、Consumer等组件,通过这些组件实现消息的生产、存储和消费。文章还提供了Spring Boot快速上手RocketMQ的示例代码,帮助读者快速入门。
|
Java Shell Linux
环境变量配置
环境变量配置
762 0
|
Java 区块链
使用Java实现区块链智能合约
使用Java实现区块链智能合约
|
小程序 安全 定位技术
高德地图实现-微信小程序地图导航
高德地图实现-微信小程序地图导航
2841 1