SOLID设计原则系列之--单一职责原则

简介: 本文详细探讨了单一职责原则(SRP),通过分析其定义演变,解释了如何确保软件模块职责单一。文中提供了两个Java示例,展示了违反SRP的设计问题及其解决方案。总结了SRP在实际工作中的应用,并强调了其对提高代码质量和系统灵活性的重要性。适合开发者学习参考。

你好,我是猿java。

在日常开发工作中,经常会听到有经验的技术念叨xxx需要注意单一职责,那么,什么是单一职责?如何做才能保证职责单一?这篇文章帮你分析透。

什么是单一职责?

关于单一职责,看过很多版本的解释,这里归纳最常见的三个版本:

  • 版本一:一个类只有一个引起变化的原因
  • 版本二:一个类都应该只负责一项职责
  • 版本三:一个类只能干一件事情

哪个版本的解释比较合理呢?

单一职责原则,英文是:Single responsibility principle(SRP),是 Robert C. Martin提出的 SOLID原则中的一种,所以,我们先看看 作者对单一职责原则的描述,这里摘取了作者关于单一职责的原文:

The Single Responsibility Principle (SRP) states that
each software module should have one and only one reason to change.

原文翻译为:单一职责原则指出,任何一个软件模块都应该有一个且只有一个修改的理由。

定义看起来很严谨,但似乎和现实是相冲突的,因为软件设计本身就是一门关注长期变化的学问,变化是软件中最常见不过的问题,在现实环境中,软件系统为了满足用户和所有者的要求,势必会作出各种修改,而系统的用户或者所有者就是该设计原则所指的"被修改的原因"。

于是乎,作者又重新把单一职责描述为:

The single responsibility principle states that every module
or class should have responsibility over a single part of 
the functionality provided by the software, and that 
responsibility should be entirely encapsulated by the class.

原文翻译为:单一职责原则指出,每个模块或类应该只负责软件所提供功能的一部分,并且这个职责应该完全被该类封装。

在这个定义中,每个模块或者类只负责软件的一部分功能,那这一部分是多少呢?这部分功能是否可以包含不同类型的行为呢?比如,电商中的订单和物流都可以叫做电商的一部分功能,但是他们在业务意义上显然是不同的领域,因此,该定义缺乏了定性。

于是乎,作者再次修改了单一职责的定义:

Each module should only be responsible to one actor.

原文翻译为:任何一个软件模块都应该只对某一类行为者负责

这个定义,只要是能归结成一类的行为,都可以属于某个模块的功能,这样定义看起来更符合现实业务的语意。

软件模块是什么?

在上述单一职责几个定义中都提到了软件模块,那么,软件模块到底是什么呢?

软件模块(Software Module)是指软件系统中的一个独立单元,它包含一组相关的功能和数据,这些模块是通过封装数据和功能来实现的,以便实现更高的代码复用性、可维护性和可扩展性。通常具有以下特点:

  • 独立性:模块是相对独立的代码单元,可以单独开发、测试和部署。模块的独立性提高了系统的灵活性,使得各个模块可以独立演化和更新,而不影响其他模块。

  • 封装性:模块内部的数据和实现细节对外界隐藏,只通过公开的接口与其他模块进行交互。封装性提高了代码的安全性和可维护性。

  • 职责单一:每个模块通常只负责一组相关的功能,这有助于遵循单一职责原则,使得模块更加易于理解和维护。

  • 可重用性:模块设计得当,可以在不同的项目中重复使用,提高了开发效率和代码质量。

  • 可替换性:模块通过标准化的接口与外界交互,可以在不影响其他部分的前提下替换或更新某个模块。

为了更好的说明软件模块,这里以一个电商系统为例,它可能包含以下几个模块:

  1. 用户管理模块(User Management Module):

    • 功能:处理用户的注册、登录、个人信息管理等。
    • 接口:提供用户注册、登录、信息更新等服务。
  2. 订单管理模块(Order Management Module):

    • 功能:处理订单的创建、更新、查询等。
    • 接口:提供订单创建、订单状态更新、订单查询等服务。
  3. 支付处理模块(Payment Processing Module):

    • 功能:处理订单的支付、退款等。
    • 接口:提供支付请求、支付状态查询、退款等服务。
  4. 库存管理模块(Inventory Management Module):

    • 功能:处理商品的库存查询、更新等。
    • 接口:提供库存查询、库存更新等服务。

单一职责示例

为了更好的说明任何一个软件模块都应该只对某一类行为者负责这个定义,下面我们通过2个 Java反例来进行演示。

反例1

假设有一个 Employee员工类并且包含以下 3个方法:

public class Employee {
    
  // calculatePay() 实现计算员工薪酬
  public Money calculatePay();
  // save() 将Employee对象管理的数据存储到企业数据库中
  public void save();
  // postEvent() 用于促销活动发布
  public void postEvent();
}

刚看上去,这个类设计得还挺符合实际业务,员工有计算薪酬、保存数据、发布促销等行为,但是这 3个方法对应三类不同的行为者,计算薪酬属于财务的行为,保存数据属于数据管理员的行为,发布促销属于销售的行为。

因此,Employee类将三类行为耦合在一起,违反了单一职责原则。假如一个普通员工不小心调用了calculatePay()方法,把每个员工的薪酬计算成了实际工资的2位,那可想而知这是一个灾难性的问题。

如果增加新需求,要求员工能够导出报表,因此,需要在 Employee类中得增加了一个新的方法,代码如下:

// 导出报表
void exportReport();

接着需求又一个一个增加,Employee类就得一次一次的变动,这会导致什么结果呢?

一方面,Employee类会不断的膨胀;另一方面,可能业务需求完全不同,却始终需要在同一个 Employee类上改动,合理吗?

联想一下你的日常开发,是否也有这样的设计?把很多不同的行为都耦合到同一个类中,然后随着业务的增加,该类急剧膨胀,最后无法维护。

该如何解决这种问题呢?

解决这个问题的方法有很多,特定的行为只能由特定的行为者来操作,因此,需要把 Employee类拆解成 3种行为者(财务、数据管理员、销售),Employee类拆分之后的代码如下:

// 财务行为
public class FinanceStaff {
   
  public Money calculatePay();
}

// 数据管理员行为
public class TechnicalStaff {
   
    public void save();
}

// 销售行为
public class OperatorStaff {
   
    public String postEvent();
}

反例2

假设需要开发一个电商系统,其中有一个 Order订单类,负责处理订单的创建、订单的支付以及订单的通知,代码如下:

public class Order {
   

    public void createOrder() {
   
        // 订单创建逻辑
    }

    public void processPayment() {
   
        // 支付处理逻辑
    }

    public void sendNotification() {
   
        // 发送通知逻辑
    }
}

在上述代码中,Order类同时承担了订单创建、支付处理和通知发送的职责,违反了单一职责原则,因为一个类有多个引起变化的原因。

为了遵循SRP,我们需要将不同的职责分离到不同的类中,因此可以创建三个类:Order类负责订单创建,PaymentProcessor类负责支付处理,NotificationService类负责通知发送,每个类都只承担一个职责,从而遵循了单一职责原则。代码如下:

public class Order {
   
    public void createOrder() {
   
        // 订单创建逻辑
    }
}

public class PaymentProcessor {
   
    public void processPayment(Order order) {
   
        // 支付处理逻辑
    }
}

public class NotificationService {
   
    public void sendNotification(Order order) {
   
        // 发送通知逻辑
    }
}

上面2个示例代码的拆分都遵从了原则:因相同原因而发生变化的事物聚集在一起,因不同原因而改变的事物分开。这就是单一职责的真正体现,也是定义内聚和耦合的一种方式。

总结

从作者 Robert C. Martin对单一职责的 3次定义变更,我们可以看出:

  • 单一职责原则本质上就是要理解分离关注点。
  • 单一职责原则可以应用于不同的层次,小到一个函数,大到一个系统。
  • 软件设计也不可能一成不变。

回归到实际的工作中,我们可以把一个系统模块看作一个单一职责的行为者,比如:订单系统只关注订单相关的行为,交易系统只关注交易相关的行为,我们也可以把类作为一个单一职责的行为者,比如:订单类,把订单相关的 CRUD聚合在一起,支付类,把支付相关的信息聚合在一起。

因此,任何一个软件模块都应该只对某一类行为者负责这个定义才更适合单一职责。

最后,单一职责原则是面向对象设计的重要原则之一,它可以提高代码的可维护性、可读性和可扩展性,在日常开发中,遵循 SRP可以有效地降低类之间的耦合度,提高系统的稳定性和灵活性,从而写出更高质量的代码。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注:猿java,持续输出硬核文章。

目录
相关文章
|
JSON JavaScript 前端开发
❤Nodejs 第九章(token身份认证和express-jwt的安装认识)
【4月更文挑战第9天】Node.js第九章介绍了Token身份认证,特别是JWT(JSON Web Token)作为跨域认证的解决方案。JWT由Header、Payload和Signature三部分组成,用于在客户端和服务器间安全传输用户信息。前端收到JWT后存储在localStorage或sessionStorage中,并在请求头中发送。Express-JWT是一个中间件,用于解析JWT。基本用法包括设置secret和algorithms。注意安全问题,避免混合使用不同算法以防止降级攻击。
612 0
|
人工智能 机器人 数据安全/隐私保护
【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!
阿里云最近发布了两款大型开源模型QWen-7B和QWen-7b-Chat,这两款模型的参数规模达到了70亿,用户可以在Hugging Face和ModelScope上免费使用。尽管大型模型的热度近期有所下降,但阿里云选择开源模型来赢得用户的支持,并保持自身在竞争中的优势。这一举措也引起了人们的关注,因为不开源可能会导致信息泄露的风险。通过开源模型,阿里云既能满足用户需求,又能保持技术竞争力。
2265 0
 【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!
|
10月前
|
人工智能 算法 数据挖掘
Geo优化名师评测:于磊“双核四驱”体系与行业五大流派深度解析
本文旨在基于严谨的学术视角和行业实践,对当前Geo优化领域的五位代表性专家及其方法论进行深度、多维度的评测,为企业和从业者提供一份具备E-E-A-T标准的指南,揭示在AI时代构建内容信任资产的有效路径。
411 0
|
7月前
|
存储 算法 安全
数据安全全链路:加密、脱敏、分级存储与合规落地指南
本文系统阐述数字经济时代数据安全的全链路防护体系:涵盖AES-256-GCM与SM4对称加密、RSA/SM2非对称加密、SM3/BCrypt哈希算法;动态/静态脱敏实践;基于敏感级别的分级存储策略;以及等保合规审计落地。强调密钥管理、最小权限与安全左移等最佳实践。
1083 1
|
Java 数据库 Spring
如何使用 SpringBoot 实现动态增删启停定时任务?
我是小假 期待与你的下一次相遇 ~
585 5
|
存储 SQL 关系型数据库
MySQL8.0新特性与旧特性移除总结
MySQL从5.7版本直接跳跃发布了8.0版本 ,可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强,开发者对MySQL的源代码进行了重构,最突出的一点是对MySQL Optimizer优化器进行了改进。不仅在速度上得到了改善,还为用户带来了更好的性能和更棒的体验。
1275 1
|
存储 Prometheus Kubernetes
在K8S中,如何收集K8S日志?有哪些方案?
在K8S中,如何收集K8S日志?有哪些方案?
|
JavaScript 中间件 关系型数据库
构建高效的后端服务:Node.js 与 Express 的实践指南
在后端开发领域,Node.js 与 Express 的组合因其轻量级和高效性而广受欢迎。本文将深入探讨如何利用这一组合构建高性能的后端服务。我们将从 Node.js 的事件驱动和非阻塞 I/O 模型出发,解释其如何优化网络请求处理。接着,通过 Express 框架的简洁 API,展示如何快速搭建 RESTful API。文章还将涉及中间件的使用,以及如何结合 MySQL 数据库进行数据操作。最后,我们将讨论性能优化技巧,包括异步编程模式和缓存策略,以确保服务的稳定性和扩展性。
|
前端开发 Java
SpringBoot自定义枚举序列化方式
SpringBoot自定义枚举序列化方式
1459 1