了解一些经典的设计原则,并且将其应用到我们的日常开发中,会大大提高代码的优雅性、可扩展性、可维护性。
本文总结了极客时间上的 《设计模式之美》 专栏中的SOLID设计原则的内容,用于学习和使用。
SOLID原则是由5个设计原则组成,SOLID对应每个原则英文字母的开头:
- 单一职责原则(Single Responsiblity Principle)
- 开闭原则(Open Close Principle)
- 里式替换原则(Liskov Substitution Principle)
- 接口隔离原则(Interface Segregation Principle)
- 依赖反转原则(Dependency Inversion Principle)
一、单一职责原则
1、单一职责原则定义(SRP)
单一职责原则的英文是Single Responsibility Principle,缩写为SRP。
英文描述是:
A class or module shoule have a single responsibility
翻译过来即一个类或模块只负责完成一个职责(功能)。
简单理解就是一个类、或者接口、或者模块只能负责完成一个职责或业务的功能。
举个例子:
订单类如果包含用户的详细信息,可能就会职责不单一;
又如,订单接口只应该提供订单查询、操作等API,如果再提供用户信息查询的操作,那么就可以说这个接口不符合单一职责原则。
2、如何判定类或模块的职责是否单一
上述例子中,订单类含有用户信息,职责可能就不单一。用了可能,就是因为在不同场景下,判定准则就可能不同。如:订单信息OrderDTO和用户地址信息AddressDTO应该属于不同的领域模型。但某个订单可能包含收获地址,那么订单类中就会有地址信息,在此场景下,地址信息可能就属于订单信息,可以看做职责单一。
所以判定类或模块的职责是否单一不可一概而论,需要在特定场景下做出不同的判定。
业务发展地越快,功能越丰富,类的内容就越多,类的职责也会划分的越简单。一般会以以下准则来判断类或模块是否职责单一:
- 类中的代码行数、函数、属性过多(如订单类OrderDTO中的地址信息属性过多时,就可以将地址信息从订单类OrderDTO中拆开,单独做一个类AddressDTO);
- 类依赖的其他类过多,或者依赖类的其他类过多(依赖相同功能类的可以拆分出来单独形成一个功能类);
- 私有方法过多(如大量的对时间格式、数字小数点进行操作,可以将私有方法拆分出来放入Util类中,提高复用性);
- 比较难给类起一个合适的名字;
- 类中大量的方法都是集中操作类中的某几个属性。
当然,类也不是拆分越细越好,也遵守"高内聚、低耦合"的原则进行拆分,否则会影响代码的可维护性。
二、开闭原则(OCP)
开闭原则是开发中最难理解和使用,但又是最有用的设计原则之一,是考量代码扩展性的。
1、开闭原则定义
开闭原则英文是:Open Close Principle,简写为OCP。
英文描述是:
Software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。
翻译过来即:
软件实体(模块、类、方法等)应该对"扩展开发,对修改关闭"。
通俗点讲即:
添加一个新的功能应该是在已有代码基础上进行扩展代码(新增模块、类、方法等),而不是修改已有代码(如修改接口定义、类信息、方法参数等)。
2、开闭原则在开发中应用
实际上,开闭原则在我们开发中使用的比较多,常见的例子就是handlers的例子。
下面是一个Api告警的demo示例,当项目中接口调用量、QPS超过一定阈值后就会告警:
/**
* Created by wanggenshen
* Date: on 2019/12/11 09:55.
* Description: API 接口监控
*/
public class ApiAlert {
private AlertRule alertRule;
private Notification notification;
private static final String NOTIFY_MSG = "【%s】api:[%s] tps exceed max tps";
public ApiAlert(AlertRule alertRule, Notification notification) {
this.alertRule = alertRule;
this.notification = notification;
}
/**
* 是否需要发送告警
*
* @param api 接口名
* @param requestCount 接口调用量
* @param errorCount 接口调用失败次数
* @param durationSeconds 窗口期
*/
public void check(String api, long requestCount, long errorCount, long durationSeconds) {
AlertRule alertRule = AlertRule.getMatchedRule(api);
// calculate tps, to evaluate if need to send URGENCY notify
long tps = requestCount / durationSeconds;
if (tps > alertRule.getMaxTps()) {
String notifyMsg = String.format(NOTIFY_MSG, "URGENCY", api);
notification.notify(NotificationEmergencyLevelEnum.URGENCY.getCode(), notifyMsg);
}
// calculate errorCount, to evaluate if need to send URGENCY notify
if (errorCount > alertRule.getMaxErrorLimit()) {
String notifyMsg = String.format(NOTIFY_MSG