设计原则之单一职责原则(SRP) 傷城~ 2022-01-17 12:25 288阅读 0赞 [为什么80%的码农都做不了架构师?>>> ][80_] ![hot3.png][] ## 简介 ## 单一职责原则是最重要的设计原则,也是最抽象的设计原则。小到函数,大到平台的设计,都可以使用单一职责原则来指导。也正因为它的抽象性,没有一个统一的规则,不同的人即使是设计同一个功能,所划分的函数、类也都是不相同的。 ## 定义 ## 单一职责原则,英文名称 `Single Responsibility Principle`,意为每一个模块、类、函数应当只具备一个职责,也即只有一个功能。按照马丁大叔的说法:“一个类的改变只有一个理由”。 这个原则只给了我们一个方向,就跟“听过很多道理依然过不好这一生”中的道理一样,为什么依然过不好?因为道理仅仅是一个道理而不具备可操作性,没有办法按照步骤一二三来得到想要的结果。 单一不需要解释,关键是职责,一个函数、接口、类、模块要干多少活才算是职责单一?多大的粒度是合适的呢? 按照我现阶段的知识水平,单一职责原则背后隐去的关键概念是[抽象][Link 1],函数、接口、类需要符合自己所在的抽象层次,在其自身所在的层面上内聚成领域,这就是自己的职责。 ## 实践 ## 需求:做一个登录功能,要求有过滤黑名单,登录成功后发送短信、邮件等功能。 注:仅示意 public class LoginManager { public String login(String userId, String password) { List<String> blacklist = blacklistService.findByUserId(userId); if(CollectionUtils.isNotEmpty) { return "user blocked"; } User user = userService.findByUserId(userId); if (user == null) { return "user not exists"; } String passwordMd5 = Md5Utils.md5(password); if (!passwordMd5.equals(user.getPassword()) { return "user login failed"; } String uuid = UUIDUtils.getUUID(); cacheService.set(uuid, userId); setCookie("sessionId", uuid); // mail related logic String mailContent = user.getUserName + "! Welcome back. From mail." mailService.send(user.getMail(), mailContent); // msg related logic String smsContent = user.getUserName + "! Welcome back. From sms." smsService.send(user.getPhone(), smsContent); return "success"; } } ### 函数 ### 这个功能从函数名来看,并没有违反单一职责的原则,登录就是需要做这么多的事。但是从编码实现来说,已经违反了SRP。登录包含的职责有过滤、校验,但是过滤、校验的具体细节并不在登录函数的职责范围内,据此重构登录函数 public class LoginManager { public String login(String userId, String password) { Pair<Boolean, String> check = loginCheck(userId, password); if (!check.left()){ return check.right(); } saveUserSesssion(userId); afterLogined(userId); return "success"; } private Pair<Boolean, String> loginCheck(String userId, String password) { Pair<Boolean. String> beforeCheck = loginBeforeCheck(userId); if(!before.left()){ return beforeCheck; } return userCheck(userId, password); } private Pair<Boolean, String> loginBeforeCheck(String userId){ List<String> blacklist = blacklistService.findByUserId(userId); if(CollectionUtils.isNotEmpty) { return Pair.of(false, "user blocked"); } return Pair.of(true, ""); } private Pair<Boolean, String> userCheck(String userId, String password){ User user = userService.findByUserId(userId); if (user == null) { return Pair.of(false, "user not exists"); } String passwordMd5 = Md5Utils.md5(password); if (!passwordMd5.equals(user.getPassword()) { return Pair.of(false, "user login failed"); } return Pair.of(true, ""); } private void saveUserSesssion(String userId){ String uuid = UUIDUtils.getUUID(); cacheService.set(uuid, userId); setCookie("sessionId", uuid); } private void afterLogined(User user) { User user = userService.findByUserId(userId); sendMail(user); sendSms(user); } private void sendMail(User user) { // mail related logic String mailContent = user.getUserName + "! Welcome back. From mail." mailService.send(user.getMail(), mailContent); } private void sendSms(User user) { // msg related logic String smsContent = user.getUserName + "! Welcome back. From sms." smsService.send(user.getPhone(), smsContent); } } 重构完成后,如果需要增加过滤条件,则只需要修改`loginBeforeCheck` 函数,如果需要增加登录后功能,则只需要修改 `afterLogined` 函数,每个函数都只有一个修改的理由,也即符合 SRP 原则。 ### 类与接口 ### 当我们将功能从函数的粒度重构之后,每个函数只负责了自己的部分,已经符合了 SRP 原则,但是从类的角度来看,登录类承担了太多的功能。增加校验规则需要修改登录类,增加登录后的功能也需要修改登录类,因此类也需要按照 SRP 的原则来进行重构。 在思考函数重构的过程中,我们已经对如何划分类有了思考。校验可以抽出来,登录后发短信、邮件也可以抽出来,这样登录类就符合了自己的名称:仅关心登录的细节。 public interface LoginCheckService { public Pair<Boolean, String> check(String userId, String password); } public interface LoginListener{ public void afterLogin(LoginEvent event); } 光有这两个类可能是不够的,我们还需要定义一个登录事件`LoginEvent`, 事件注册中心 `Registry`, 事件分发`Dispatcher`, `LoginCheckService` 是有先后顺序的要求的,可以实现一个 `Order` 接口,也可以拆成两个接口,同一个接口的实现没有顺序要求。这完全取决于我们系统功能的规模,和我们对职责的认识。 ### 模块 ### 虽然登录功能一般不会做成模块,但我们可以站在模块的角度来思考。模块是大家共用的依赖,对于可扩展性、可维护性要求会比一个功能要求更高。在 `类和接口` 小节的描述中,事件、注册中心等在功能层面上可能不是必须的,在模块层面上,这些是必须的。没有事件,使用方就不知道如何响应;没有注册中心,使用方就不知道如何定制化;没有事件分发,模块就无法将事件通知到使用方。 ## 缺点 ## SRP 可以很好的将我们的功能、应用解耦,但是应该看到 SRP 存在的缺点,才可以更好的权衡自己的设计。 * 不明确。职责的含义没有明确界定,如何界定是门艺术。 * 无评判标准。界定出来的职责是好是坏?没有标准,只有经验。 * 易滥用。职责划分到最后可能就是一个接口一个方法,看似符合 SRP,实则是 SRP 的滥用。 * 函数、接口、类爆炸。 * 知识比较支离。信息分布在各个类中,不如放在一起集中。 ## 后记 ## 要做一个符合SRP 原则的设计是很困难的,需要我们在实践中总结经验。对一个领域有了充分的了解,我们才能更加游刃有余的应用SRP 原则。同时不要滥用 SRP原则,编程是门艺术,设计更是一门艺术。 ## 个人 ## 个人公众号 ![dfd177307b2212dfda8433e37afc01d835f.jpg][] 转载于:https://my.oschina.net/liufq/blog/3058884 [80_]: https://my.oschina.net/u/2663968/blog/3051541 [hot3.png]: /images/20220117/fe5c9526e9dd4b2c8778dd9ac63157bf.png [Link 1]: https://my.oschina.net/liufq/blog/3043509 [dfd177307b2212dfda8433e37afc01d835f.jpg]: /images/20220117/b55fe5a4117941a1b1aabefd202e2754.png
相关 面向对象设计原则:单一职责原则(SRP)实例 单一职责原则(SRP)是一种编程和软件设计原则,它要求一个类或模块只负责一项特定的任务。 实例: 1. **数据库操作类**: - 如果这个类负责了连接数据库、执行查 青旅半醒/ 2024年09月10日 13:21/ 0 赞/ 25 阅读
相关 【设计模式】设计原则:SRP 单一职责原则 单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。 假设我们有一个 Class 负责两个职责,一旦发生需求 浅浅的花香味﹌/ 2023年01月14日 05:58/ 0 赞/ 94 阅读
相关 面向对象设计原则(一):单一职责原则(SRP) 面向对象设计原则(一):单一职责原则(SRP) > 单一职责原则(Single responsibility principle,SRP)是面向对象设计(OOD 今天药忘吃喽~/ 2022年07月10日 04:20/ 0 赞/ 160 阅读
相关 设计模式原则—单一职责原则(SRP) 定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 也就是说,一个类应该只有一个职责。如果有多个职责,就相当于把这些职责耦合在一起,一个职责的变化会削弱 雨点打透心脏的1/2处/ 2022年06月17日 00:48/ 0 赞/ 179 阅读
相关 单一职责原则(SRP) > 《敏捷软件开发:原则、 模式与实践》中这样描述:就一个类而言,应该仅有一个引起它变化的原因。 在SRP中,把类职责定义为“变化的原因”(a reason for ch 约定不等于承诺〃/ 2022年06月12日 11:14/ 0 赞/ 175 阅读
相关 函数的单一职责原则(SRP) 结构化编程强调单一出口的原则,其目的在于增强函数流程的逻辑性。本身这个原则有些过于死板,但其保持代码逻辑性的目的是非常正确的。在面向对象设计,我们都知道要遵循单一职责原则(SR 我会带着你远行/ 2022年06月06日 02:55/ 0 赞/ 185 阅读
相关 设计原则之单一职责原则 前言:暑假匆匆过了一边Java的设计模式,现在来系统总结一下。毕竟写Java程序不懂设计模式根本就是瞎扯,并且懂了设计模式学一些框架也更加快嘛,要知道里面可是设计模式满天飞。 小咪咪/ 2022年06月05日 04:59/ 0 赞/ 193 阅读
相关 设计原则之单一职责原则(SRP) [为什么80%的码农都做不了架构师?>>> ][80_] ![hot3.png][] 简介 单一职责原则是最重要的设计原则,也是最抽象的设计原则。小到函数,大到平台的 傷城~/ 2022年01月17日 12:25/ 0 赞/ 289 阅读
相关 设计原则:单一职责原则 单一职责原则(SRP) 单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描述是这样的:A class 忘是亡心i/ 2021年09月21日 14:00/ 0 赞/ 381 阅读
还没有评论,来说两句吧...