文章目录

  • 什么是单一职责原则
  • 案例
  • 如何判断一个类的职责是否单一?
  • 类的职责是否越单一越好?
  • 总结
  • 参考资料

什么是单一职责原则

单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描述是这样的:A class or module should have a single responsibility。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。

一个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。

案例

// IUserInfoDao
User selectUser(String id);
List<User> selectUsers();
int insertOne(User user);
int updateOne(User user);
Org selectOrg(String id);
int selectOrgCount(Org org);

相信很多小伙伴一眼就看出以上用户接口有问题了,即用户和机构没有分开设计。也就是说,接口的职责并不是单一的,而是包含两个职责(功能),用户机构。

如果按照单依职责原则,应该将用户和机构分开,关于用户的操作放在一个接口,关于机构的操作放在另一个接口。

但是很多时候,一个类的职责划分的并没有很明确,比如说:

// IUserInfoDao
User selectUserAndOrg(String id);// User
private String id;
private String name;
private String mobile;
...
private String orgId;
private String orgName;

当一个关联查询涉及user信息、org信息时,此时将这样一个操作同时放在User类中合不合适?这样是否违背单一职责原则呢?

有人会说,对于org的信息来说,可能对user只是展示的作用,放在user类中并无不可;也有人说,org信息占比太多,应该单独抽离出一个org类。

我们可以再延伸一步,当公司做的原来越大,user信息已经拆分成两部分,原来的id、name等等基础信息为一部分,mobile、address等作为附加信息为另一部分,此时是不是应该也算是不符合单一职责原则了呢?

我们可以总结出,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。在某种应用场景或者当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果换个应用场景或着在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。

除此之外,从不同的业务层面去看待同一个类的设计,对类是否职责单一,也会有不同的认识。比如,例子中的 User 类。如果我们从“用户”这个业务层面来看,User 包含的信息都属于用户,满足职责单一原则。如果我们从更加细分的“用户展示信息”“地址信息”“登录认证信息”等等这些更细粒度的业务层面来看,那 User 就应该继续拆分。

如何判断一个类的职责是否单一?

上面的案例相信我们也都明白,评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。实际上,在真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。

下面这几条判断原则,比起很主观地去思考类是否职责单一,要更有指导意义、更具有可执行性:

  • 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,我们就需要考虑对类进行拆分;
  • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分;
  • 私有方法过多,我们就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性;
  • 比较难给类起一个合适名字,很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰;
  • 类中大量的方法都是集中操作类中的某几个属性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来。

类的职责是否越单一越好?

Serialization 类实现了一个简单协议的序列化和反序列功能,具体代码如下:


/*** Protocol format: identifier-string;{gson string}* For example: UEUEUE;{"a":"A","b":"B"}*/
public class Serialization {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Serialization() {this.gson = new Gson();}public String serialize(Map<String, String> object) {StringBuilder textBuilder = new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}public Map<String, String> deserialize(String text) {if (!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr = text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);}
}

如果我们想让类的职责更加单一,我们对 Serialization 类进一步拆分,拆分成一个只负责序列化工作的 Serializer 类和另一个只负责反序列化工作的 Deserializer 类。拆分后的具体代码如下所示:


public class Serializer {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Serializer() {this.gson = new Gson();}public String serialize(Map<String, String> object) {StringBuilder textBuilder = new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}
}public class Deserializer {private static final String IDENTIFIER_STRING = "UEUEUE;";private Gson gson;public Deserializer() {this.gson = new Gson();}public Map<String, String> deserialize(String text) {if (!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr = text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);}
}

虽然经过拆分之后,Serializer 类和 Deserializer 类的职责更加单一了,但也随之带来了新的问题。如果我们修改了协议的格式,数据标识从“UEUEUE”改为“DFDFDF”,或者序列化方式从 JSON 改为了 XML,那 Serializer 类和 Deserializer 类都需要做相应的修改,代码的内聚性显然没有原来 Serialization 高了。而且,如果我们仅仅对 Serializer 类做了协议修改,而忘记了修改 Deserializer 类的代码,那就会导致序列化、反序列化不匹配,程序运行出错,也就是说,拆分之后,代码的可维护性变差了。

实际上,不管是应用设计原则还是设计模式,最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候,也可以以此作为最终的考量标准。

总结

我们常说的“高内聚”,“低耦合”,其实相关的都放在一起叫高内聚,不放在一起叫低内聚;不相关的放在一起叫高耦合,不相关的不放在一起叫低耦合。

很多设计原则并不是一定要朝着这个方向去设计,而是随着当前的业务、以及靠着自己的经验来判断。这并不是一个1+1=2定性的东西,而是非常灵活。

所以说,掌握了设计原则,靠的是大量的实践。

参考资料

王争老师《设计模式之美》

设计原则之【单一职责原则】相关推荐

  1. Java设计原则之单一职责原则、开闭原则、里氏代换原则

    文章目录 面向对象设计原则概述 单一职责原则 开闭原则 里氏代换原则 面向对象设计原则概述 软件的可维护性(Maintainability)和可复用性(Reusability)是两个非常重要的用于衡量 ...

  2. 设计原则:单一职责原则

    单一职责原则(SRP) 单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP.这个原则的英文描述是这样的:A class or module shoul ...

  3. 经典设计原则:单一职责原则(SRP)

    本文详解设计原则中的单一职责原则,目的还是提高代码的可读性.可扩展性.复用性.可维护性等. 目录 1. 单一职责原则(SRP) 2. 如何理解单一职责原则? 3. 如何判断类的职责是否足够单一? 4. ...

  4. 面向对象的七种原则:单一职责原则,开放关闭原则

    我们的知识星球马上就要开始更新设计模式了,在更新设计模式之前,我们是不是需要做一些准备呢?否则设计模式中一些遵循的原则大家会一头雾水,所以我今天来给大家说一些面向对象的七种原则,有人说是6种有人说是7 ...

  5. SOLID原则:单一职责原则(SRP)

    SOLID:SOLID 原则并非单纯的1个原则,而是由5个设计原则组成,它们分别是:单一职责原则.开闭原则.里式替换原则.接口隔离原则和依赖反转原则,SOLID 由5个设计原则的头一个字母组成. 如何 ...

  6. 设计模式六大原则(一)----单一职责原则

    设计模式六大原则之[单一职则原则] 一.什么是单一职责原则 首先, 我们来看单一职责的定义. 单一职责原则,全称Single Responsibility Principle, 简称SRP. A cl ...

  7. 接口隔离原则和单一职责原则区别

    接口隔离原则和单一职责原则区别 单一职责原则是备受争议的原则,根据不同的业务逻辑,它会将系统功能模块划分成不同种类,产生多样的接口,同时每个接口尽量只包含一个功能(方法). 而产生争议的原因就是这个业 ...

  8. 6大设计原则之单一职责原则

    单一职责原则 如果有一个用户管理类,类图如下 我想,任谁也能看的出这个接口设计的有问题,用户的属性和用户的行为没有分开,应该把用户的信息抽取成一个业务对象,把用户的行为抽取成一个业务对象,按照这个思路 ...

  9. 架构中的设计原则之单一职责原则 - 《java开发技术-在架构中体验设计模式和算法之美》...

    2019独角兽企业重金招聘Python工程师标准>>> 单一职责模式: 单一职责原则的核心思想就是:系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成. ...

  10. 设计模式---面向对象设计原则之单一职责原则

    单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小.单一职责原则定义如下: 单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领 ...

最新文章

  1. ORA-19809: 超出了恢复文件数的限制
  2. Windows软件路由器典型应用:静态路由、RIP和包过滤
  3. 架构师之路 — API 经济 — RESTful API
  4. linux C 中的volatile使用
  5. AndroidStudio自动补完包的快捷键
  6. Host Switch Plus结合nginx使用
  7. 关于内核页表和进程页表的一个问题
  8. 学而不思则罔 - SAP云平台ABAP编程环境的由来和适用场景
  9. 创建一个 Git 版本库
  10. python asyncio_Python 中的异步编程:Asyncio
  11. Redmi K50标准版工信部入网:搭载骁龙870 没有12GB内存
  12. ubuntu安装composer
  13. 用java代码将从数据库中取出的具有父子关系的数据转成json格式
  14. html中怎么给文字设置动画效果,文字效果怎么设置?
  15. 数据分析的价值是什么?如何做好数据分析?
  16. 网易云音乐前端性能监控实践
  17. SCI-HUB最新文献网站
  18. 为什么你不能体面地做自己
  19. MySQL遇到错误:1217 - Cannot delete or update a parent row: a foreign key constraint fails
  20. 网站微信扫码支付流程

热门文章

  1. 第7期:内卷和躺平,你怎么选
  2. linux下的sed用于分类标签的建立
  3. 新版markdown功能发布!支持github flavored markdown!
  4. 500 (Internal Server Error)
  5. 为什么深度学习(Deep Learning)要使用GPU而不是CPU?
  6. Uncaught TypeError: Found non-callable @@iterator
  7. arcgispro3.1(账号登陆)
  8. 计算机专业学生毕业去大公司好还是小公司好?
  9. 永久勘误:微软等面试100题系列,答案V0.4版[第41-60题答案]
  10. 蓝桥ROS机器人之STDR沿墙跑