单一职责原则是最重要的设计原则,也是最抽象的设计原则。小到函数,大到平台的设计,均可以使用单一职责原则来指导。也正由于它的抽象性,没有一个统一的规则,不一样的人即便是设计同一个功能,所划分的函数、类也都是不相同的。java
单一职责原则,英文名称 Single Responsibility Principle
,意为每个模块、类、函数应当只具有一个职责,也即只有一个功能。按照马丁大叔的说法:“一个类的改变只有一个理由”。编程
这个原则只给了咱们一个方向,就跟“听过不少道理依然过很差这一辈子”中的道理同样,为何依然过很差?由于道理仅仅是一个道理而不具有可操做性,没有办法按照步骤一二三来获得想要的结果。session
单一不须要解释,关键是职责,一个函数、接口、类、模块要干多少活才算是职责单一?多大的粒度是合适的呢?函数
按照我现阶段的知识水平,单一职责原则背后隐去的关键概念是抽象,函数、接口、类须要符合本身所在的抽象层次,在其自身所在的层面上内聚成领域,这就是本身的职责。ui
需求:作一个登陆功能,要求有过滤黑名单,登陆成功后发送短信、邮件等功能。编码
注:仅示意spa
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。登陆包含的职责有过滤、校验,可是过滤、校验的具体细节并不在登陆函数的职责范围内,据此重构登陆函数.net
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 的原则来进行重构。code
在思考函数重构的过程当中,咱们已经对如何划分类有了思考。校验能够抽出来,登陆后发短信、邮件也能够抽出来,这样登陆类就符合了本身的名称:仅关心登陆的细节。
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原则,编程是门艺术,设计更是一门艺术。
我的公众号
个人博客即将同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1nov2ydr8zly