SOLID 原则,实际上是由 5个设计原则组成的,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则, 为了方便记忆是那个五个设计原则:依次取五个设计原则的首字母 S、O、L、I、D 这 5 个英文字母命名。
单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。这个原则的英文描述是这样的:A class or module should have a single reponsibility。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。单一职责原则的核心就是解耦和增强内聚性
。
怎么判断一个类是否满足单一原则是比较好判断的,但是在设计以及编程的时候,大脑里怎么样区分以及保证编程满足单一原则呢?
为什么一直在强调的单一职责,而实际代码中却屡屡违反呢?
如何判断类的职责是否足够单一?
在设计中很难明确区分是否满足单一,也要结合实际的情况来考虑。
js
private String username;
private String password;
private String pubsubTopic;
private int pubsubPort;
private Boolean isPc;
private PubsubClient pubsubClient;
private TelemetryTool telemetryTool;
分析上面的这个例子,这个例子里包含账号密码,包含常用的打点参数,
-
如果整个项目对于安全层级要求较高,要存取在安全秘钥或者秘钥服务器中,那么
username
,password
,就需要自己封装一个类,并且有有网络请求以及加解密和校验逻辑在里面。 -
假如说目前对于账号密码的要求较低,并且用户名和密码也不会对业务有很大的影响,单纯是数据查看,并且数据几乎不会变动,那么用户名和密码完全可以存在于Pubsub初始化处理类。
从刚刚这个例子,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,结论是不一样的。在当下的需求背景下,一个类的设计可能已经满足单一职责原则了,但如果在未来的某个需求背景下,可能就不满足了,需要继续拆分成粒度更细的类。
根据《代码整洁之道》和网上的例子,下面的情况就需要对类进行重构和拆分了:
- 当类中的代码行数很多时,鼠标滚轮需要滚半天,影响代码的可读性和可维护性,就需要考虑拆分了。
- 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,我们就需要考虑对类进行拆分;
- 当给类命名时,发现他既有这个功能,也有那个功能,那么就需要考虑下是不是职责不够清晰,没有遵循单一职责。
编程过程这能够怎么注意实现呢?
其实在我们调用很多模块的方法和监听对象时,想一想,很熟悉的就是继承某个接口,重写某个方法,来单独实现我们需要的功能,我想这就是单一职责的具体体现吧。
1. 方法
js
public interface AccountOperate {
void updateUserName(UserInfo userInfo);
void updateUserPassword(UserInfo userInfo);
}
js
public class AccountOperateImpl implements AccountOperate {
@Override
public void updateUserName(UserInfo userInfo) {
// 修改用户名逻辑
}
@Override
public void updateUserPassword(UserInfo userInfo) {
// 修改密码逻辑
}
}
2. 接口
想起来前段时间刚刚加入的业务,有2个弹窗,一个是业务开启的弹窗,另一个是同一隐私的弹窗,两个弹窗前面都是实现的同一个接口
js
/** * 弹窗接口 */
public interface Dialog {
// 开启业务
void agree();
// 不同意
void disAgree();
}
//实现-首页
public class ShouPage implements Dialog{
@Override
public void agree() {
// 开启业务
}
@Override
public void disAgree() {
// 不同意
}
}
//实现-设置页面
public class SettingPage implements Dialog{
@Override
public void agree() {
// 开启业务
}
@Override
public void disAgree() {
// 不同意
}
}
刚刚开始业务所有开启入口都实现这个接口,来对用户同意开启业务和拒绝业务,开启隐私和拒绝隐私做处理。后来产品对业务做了调整,但是呢,只对用户开启页面做了修改,隐私页面没有。特别是需要调用接口传参数,连不需要修改的隐私页面也被动做了修改。当时对单一职责不太了解。那现在学习了,应该怎么改呢?
js
/** * 弹窗接口 */
public interface ProjectDialog {
// 开启业务
void agree(int num);
// 不同意
void disAgree();
}
public interface CookieDialog {
// 开启业务
void agree();
// 不同意
void disAgree();
}
//实现-首页
public class ProjectPage implements Dialog{
@Override
public void agree(int num) {
// 开启业务
}
@Override
public void disAgree() {
// 不同意
}
}
//实现-设置页面
public class SettingPage implements CookieDialog{
@Override
public void agree() {
// 开启业务
}
@Override
public void disAgree() {
// 不同意
}
}
应该这样设计,不应该贪图省事就混为一谈,导致后面的人修改的时候没有办法进行修改,虽然代码review中,说这一点小修改也没有什么,但是现在想想,确实挺低级的,对自己的应该有一定的要求。
3. 类
因为类是起初实现代码的地方,不能单独为了满足单一职责原则,是不是把类拆得越细就越好呢?答案是否定的。
比如说上面的例子,同意开启隐私和不同意开启隐私,在类中也需要执行单一原则的话,需要拆分成2个类。但是也没有其他的逻辑来实现,那这样显然是多余的。
js
/** * 弹窗接口 */
public interface ProjectDialog {
// 开启业务
void agree(int num);
// 不同意
void disAgree();
}
//实现-首页
public class ProjectPage implements Dialog{
@Override
public void agree(int num) {
// 开启业务
//写入缓存,记录开关状态
new ProjectInitManger().initProject()
}
@Override
public void disAgree() {
// 不同意
//清除缓存,记录开关状态
new ProjectInitManger().resetProject(f)
}
}
js
public class ProjectInitManger{
//写入缓存,记录开关状态
public void initProject()
//清除缓存,记录开关状态
public void initProject()
}
上面的例子就是对开启应用时状态的记录,没有其他额外的功能,如果强制遵循单一职责的话,那就需要再拆分为2个类,并没有实际的意义。
单一职责原则的优点
- 类的复杂性降低: 一个类实现什么职责都有明确定义, 复杂性自然就降低
- 可读性提高: 复杂性降低,可读性自然就提高
- 可维护性提高: 可读性提高,代码就更容易维护
实际中为什么还会违反单一原则?
一个是真的是对项目不了解,二是对于单一职责不清晰,三是人的惰性,共勉。