概述
为什么80%的码农都做不了架构师?>>>
简介
单一职责原则是最重要的设计原则,也是最抽象的设计原则。小到函数,大到平台的设计,都可以使用单一职责原则来指导。也正因为它的抽象性,没有一个统一的规则,不同的人即使是设计同一个功能,所划分的函数、类也都是不相同的。
定义
单一职责原则,英文名称 Single Responsibility Principle
,意为每一个模块、类、函数应当只具备一个职责,也即只有一个功能。按照马丁大叔的说法:“一个类的改变只有一个理由”。
这个原则只给了我们一个方向,就跟“听过很多道理依然过不好这一生”中的道理一样,为什么依然过不好?因为道理仅仅是一个道理而不具备可操作性,没有办法按照步骤一二三来得到想要的结果。
单一不需要解释,关键是职责,一个函数、接口、类、模块要干多少活才算是职责单一?多大的粒度是合适的呢?
按照我现阶段的知识水平,单一职责原则背后隐去的关键概念是抽象,函数、接口、类需要符合自己所在的抽象层次,在其自身所在的层面上内聚成领域,这就是自己的职责。
实践
需求:做一个登录功能,要求有过滤黑名单,登录成功后发送短信、邮件等功能。
注:仅示意
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原则,编程是门艺术,设计更是一门艺术。
个人
个人公众号
转载于:https://my.oschina.net/liufq/blog/3058884
最后
以上就是重要早晨为你收集整理的设计原则之单一职责原则(SRP)的全部内容,希望文章能够帮你解决设计原则之单一职责原则(SRP)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复