单一职责原则简介

单一职责原则简介参考文章 面向对象编程的六大原则 1 单一职责原则 1 什么是单一职责 单一职责原则 Singlerespon 简称 SRP 顾名思义 是指一个类或者一个模块应该有且只有一个职责 它是面向对象编程六大原则之一 2 单一职责的粒度 单一职责的粒度 可以是某个方法 某个类 某个程序包甚至某个功能 某个模块 甚至某个系统 每一个层次上都可以进行单一职责设计 下面来举个例子说明一下 模块级别 某购物平台 包含如下几个模块 用户模块 商品模块

参考文章:面向对象编程的六大原则(1)–单一职责原则

1. 什么是单一职责

  单一职责原则(Single responsibility principle,简称SRP),顾名思义,是指一个类或者一个模块应该有且只有一个职责,它是面向对象编程六大原则之一。

2. 单一职责的粒度

  单一职责的粒度,可以是某个方法、某个类、某个程序包甚至某个功能、某个模块(甚至某个系统),每一个层次上都可以进行单一职责设计。下面来举个例子说明一下:

  模块级别:某购物平台,包含如下几个模块:用户模块、商品模块、订单模块、库存模块、运输模块、支付模块,每一个模块都已本身的职责:用户模块负责用户相关的功能,商品模块负责商品的管理等等;

  功能级别:在模块内部,比如用户模块,根据用户相关职责划分,又划分为用户管理、注册、登录等不同的功能,他们的职责也都是各不相同的;

  程序包级别:在用户管理中,又可以根据层次不同划分为持久化层、业务逻辑层、UI层,其中每一层基本上就是一个程序包,持久化层负责数据持久化相关(和数据库打交道,数据的CRUD(增删改查)),业务逻辑层负责用户管理相关的业务逻辑(比如修改密码),UI层负责用户界面交互;

  类级别:在持久化层内部,又可以根据职责不同划分为:数据库连接类(负责数据库连接的构造)、持久化类(负责数据的CRUD操作)、数据缓存类(负责数据缓存的处理)等;

  方法级别:在持久化类中,又可以划分为查询数据方法(负责查询数据)、检索数据方法(负责检索数据)、新增数据方法(负责插入数据)、 删除数据方法(负责删除数据)、修改数据方法(负责修改数据)。有些复杂场景,甚至建议增删改查每一个逻辑都放到不同的类中;

  方法级别二:在查询数据方法中,又可以根据查询的逻辑步骤划分为不同的二级方法:解析查询条件方法、构造Sql方法、执行Sql方法、构造返回实体方法;每个方法可以借着这个思路再往下细化;

3. 单一职责的优点

  1. 增强功能的稳定性
    单一职责能够增强功能稳定性、健壮性,适应程序的变化,避免修改一段逻辑时,对其他逻辑造成影响。例如:一个类或者一个方法,承担了多个职责:职责A、职责B、职责C…职责N。如果在维护过程中,修改之职责A对应的逻辑,就很有可能对职责B、C等其他职责逻辑造成影响,最终不利于功能的稳定性。

  2. 提升复用能力
    使用单一职责,有一某一个类或者方法都是对应的它本身的唯一职责,已经是实现某个职责的最小粒度,所以它可以提供给任何需要该职责地方调用,从而提升了复用能力。

  3. 降低程序复杂度
    由于单一职责原则是根据职责来设计,每一个类或者方法只实现某一特定的逻辑,粒度划分的合适的话,每一个类基本上只有几行、至多几十行代码,程序复杂度会被大大降低;

  4. 提升可读性
    每一个程序包、类、方法职责明确后,我们在阅读代码时,我们可以通过方法名就能知道某个方法是干什么的(可以不用知道具体的细节),它内部用到的方法是干什么的,有助于我们在某一层次上思考代码逻辑是否正确、完整,而不用受某一具体细节逻辑的影响。

 4. 具体实践

  在用户管理持久化层,如果要适配不同类型的数据库(比如要支持SqlServer、Oracle、MySql等),不同类型的数据库,其语法结构都有一些差异,比如插入数据方法,插入sql的语法是不同的。我们可能按照如下代码实现:

  以下是用户信息的结构:

public class UserInfo { public string ID { get; set; } public string Code { get; set; } public string Name { get; set; } }

  以下是用户持久化的新增数据方法:

public class UserDac { Database GetDatabase() { /*获取Database连接的逻辑*/ } //新增用户 public void AddUser(UserInfo userInfo) { Database db = GetDatabase(); string insertSql = string.Empty; switch (db.DbType) { case DBType.SqlServer: insertSql = "Sql A"; break; case DBType.Oracle: insertSql = "Sql B"; break; default: throw new NotSupportedException("DBType"); } db.ExecuteSql(insertSql); } }

  在上面的例子中,我们看似应该是一个比较简单并且合理的结构。但是假如有以下需求场景:

  1. 业务中添加了新的字段,需要修改 InsertSql(在Sql中添加新字段部分);
  2. 我需要添加一种新的数据库类型支持(假如需要支持开源的MySql),新的数据库类型中,Sql语法也是不相同的;

  两个场景都需要修改以上的代码,这说明上面这段代码承担了两个职责,违背了单一职责原则。

  从上面的方法中,我们可以看到 ,上面方法承担了两个职责:拼接插入Sql;适配不同的数据库类型。我们可以按照如下方式进行修改(修改中用到了设计模式中的策略模式)。

  用户信息的结构不变:

public class UserInfo { public string ID { get; set; } public string Code { get; set; } public string Name { get; set; } }

   第一步:我们添加一个获取Sql的策略,如下结构:

public interface IUserSqlStrategy { string GetInsertSql(UserInfo userInfo); }

  第二步:我们添加SqlServer的策略实现:

public class SqlServerUserSqlStrategy implements IUserSqlStrategy { public string GetInsertSql(UserInfo userInfo) { return "Sql A"; } }

  第三步:相应的,我们添加Oracle、MySql的的实现类:

public class OracleUserSqlStrategy implements IUserSqlStrategy { public string GetInsertSql(UserInfo userInfo) { return "Sql B"; } } public class MySqlUserSqlStrategy implements IUserSqlStrategy { public string GetInsertSql(UserInfo userInfo) { return "Sql C"; } }

  第四步,我们添加策略工厂,来根据不同的数据库类型获取不同的策略实现:

public class UserSqlStrategyFactory { public static IUserSqlStrategy GetUserSqlStragety(DBType dbType) { switch (dbType) { case DBType.SqlServer: return new SqlServerUserSqlStrategy(); case DBType.Oracle: return new OracleUserSqlStrategy(); case DBType.MySql: return new MySqlUserSqlStrategy(); default: throw new NotSupportedException("DBType"); } } }

  最后,执行插入逻辑中,调用策略工厂获取策略,然后根据相应的策略获取不同的Sql实现:

public class UserDac { Database GetDatabase() { /*获取Database连接的逻辑*/ } //新增用户 public void AddUser(UserInfo userInfo) { Database db = GetDatabase(); var strategy = UserSqlStrategyFactory.GetUserSqlStragety(db.DbType); string insertSql = strategy.GetInsertSql(userInfo); db.ExecuteSql(insertSql); } }

  以上是所有的代码实现,通过策略模式,将不同数据库类型的实现拆分到不同的策略实现类中,这样拼接插入Sql逻辑与适配不同数据库逻辑两个职责拆分开来。

 总结

  通过单一职责原则,将程序实现拆分到不同的类(或者方法)中,这样,以后的变化都只影响要修改的主责,而不会影响到其他职责,最终达到提升稳定性的目的。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229243.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:08
下一篇 2026年3月16日 下午5:08


相关推荐

  • 7、注解@Mapper、@MapperScan

    7、注解@Mapper、@MapperScan1、@Mapper注解:作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类添加位置:接口类上面@MapperpublicinterfaceUserDAO{//代码}如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan2、@MapperScan作用:指定要变成实现类的接口所在的…

    2022年5月1日
    49
  • 怎么新建pytest的ini文件_pytest conftest.py文件

    怎么新建pytest的ini文件_pytest conftest.py文件前言pytest配置文件可以改变pytest的运行方式,它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行查看pytest.ini的配置选项pytest-h找到以下

    2022年7月30日
    8
  • 腾讯元宝怎么嵌入微信

    腾讯元宝怎么嵌入微信

    2026年3月13日
    2
  • 谷歌最强生图模型来了!NanoBanana新功能详解+使用入口

    谷歌最强生图模型来了!NanoBanana新功能详解+使用入口

    2026年3月14日
    2
  • Vim命令合集

    Vim命令合集Vim命令合集命令历史以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令。启动vim在命令行窗口中输入以下命令即可vim直接启动vimvimfilename打开vim并创建名为filename的文件文件命令打开单个文件vimfile同时打开多个文件vimfile1file2file3..

    2022年6月2日
    44
  • APP 安全测试(OWASP Mobile Top 10)–后篇之一

    APP 安全测试(OWASP Mobile Top 10)–后篇之一OWASPMobileTop10相对于Web的OWASPTop10来说,个人觉得描述的相对简单多,并且安全测试的时候的可操作性也不是太强。本来打算个人整体捋一遍的,但因为项目时间的问题,前面四个章节安排给了别人去负责,我只负责后面的六章(所以标题写了后篇)。下面我把个人的测试方法简单叙述一下。下面可能有些测试点不全或者有瑕疵,欢迎纠错。。。。OWASPM…

    2022年5月7日
    165

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号