定义
在面向对象编程领域中,开闭原则规定软件中的对象、类、模块和函数对扩展是开放的,但对于修改是封闭。这应该用抽象定义结构,用具体实现拓展细节。
简单来说,添加一个新的功能应该是在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
模拟案例
场景:API 接口监控告警功能
不同的预警有不同的紧急程度的通知
- 通知的紧急程度,包括 SEVERE(严重)、URGENCY(紧急)、NORMAL(普通)、TRIVIAL(无关紧要)。
- 预警触发规则
当接口的 TPS 超过某个预先设置的最大值时,发送紧急通知
当接口请求出错数大于某个最大允许值时,发送严重通知
新加一个规则
当每秒钟接口超时请求个数,超过某个预先设置的最大阈值时,发送严重通知
基础类
java
/**
* @description:紧急程度枚举
* @version: 1.0
* @author:blackcat
*/
public enum NotificationEmergencyLevel {
/**
* 普通
*/
NORMAL,
/**
* 紧急
*/
URGENCY,
/**
* 严重
*/
SEVERE,
/**
* 无关紧要
*/
TRIVIAL;
}
java
/**
* @description:通知模拟类
* @version: 1.0
* @create:2023/11/3 10:14
* @author:blackcat
*/
@Slf4j
public class Notification {
/**
*
* @param urgency
* @param msg
*/
public void notify(NotificationEmergencyLevel urgency, String msg) {
log.info("等级:{},内容:{}",urgency,msg);
}
}
java
/**
* @description:规则属性
* @version: 1.0
* @author:blackcat
*/
public class Rule {
/**
* 最大tps阈值
*/
private long maxTps;
/**
* 最大错误次数阈值
*/
private long maxErrorCount;
public Rule(long maxTps, long maxErrorCount) {
this.maxTps = maxTps;
this.maxErrorCount = maxErrorCount;
}
public long getMaxTps() {
return maxTps;
}
public void setMaxTps(long maxTps) {
this.maxTps = maxTps;
}
public long getMaxErrorCount() {
return maxErrorCount;
}
public void setMaxErrorCount(long maxErrorCount) {
this.maxErrorCount = maxErrorCount;
}
}
java
/**
* @description:告警规则集
* @version: 1.0
* @author:blackcat
*/
public class AlertRule {
private Map<String, Rule> ruleMap = new HashMap<>();
public AlertRule() {
register("/api/user/add", new Rule(100L, 10L));
register("/api/user/list", new Rule(1000L, 5L));
}
public void register(String api, Rule rule) {
ruleMap.put(api, rule);
}
public Rule getMatchedRule(String api) {
return ruleMap.get(api);
}
}
java
/**
* @description:api相关信息
* @version: 1.0
* @author:blackcat
*/
@Data
public class ApiInfo {
/**
* 请求api路径
*/
private String api;
/**
* 请求次数
*/
private long requestCount;
/**
* 错误次数
*/
private long errorCount;
/**
* 单位时间
*/
private long durationOfSeconds;
/**
* 新增:接口超时次数
*/
private long timeoutCount;
}
/**
* @description:告警功能实现
* @version: 1.0
* @author:blackcat
*/
public class Alert {
/**
* 存储告警规则
*/
private AlertRule rule;
/**
* 告警通知类
*/
private Notification notification;
public Alert(AlertRule rule, Notification notification) {
this.rule = rule;
this.notification = notification;
}
public void check(ApiInfo apiInfo) {
String api = apiInfo.getApi();
long requestCount = apiInfo.getRequestCount();
long durationOfSeconds = apiInfo.getDurationOfSeconds();
long errorCount = apiInfo.getErrorCount();
long tps = requestCount / durationOfSeconds;
//当接口的 TPS 超过某个预先设置的最大值时,发送通知
if (tps > rule.getMatchedRule(api).getMaxTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "TPS超过阈值发送紧急预警");
}
//当接口请求出错数大于某个最大允许值时,发送通知
if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
notification.notify(NotificationEmergencyLevel.SEVERE, "接口请求出错数超过阈值发送严重预警");
}
}
}
违背开闭原则实现
java
@Data
public class ApiInfo {
/**
* 改动一:新增:接口超时次数
*/
private long timeoutCount;
}
public class Alert {
public void check(ApiInfo apiInfo) {
//改动二 新增 当每秒钟接口超时请求个数超过阈值发送紧急预警"
long timeoutCount = apiInfo.getTimeoutCount();
long timeoutTps= timeoutCount / durationOfSeconds;
if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "当每秒钟接口超时请求个数超过阈值发送紧急预警");
}
}
}
改动一:ApiInfo:新增属性:接口超时次数 改动二:Alert的check()方法添加相关逻辑 改动三:Rule添加单位时间内最大超时次数阈值属性, AlertRule赋值单位时间内最大超时次数阈值属性
开闭原则优化
抽象告警具体器
java
/**
* @description:告警处理器抽象
* @version: 1.0
* @author:blackcat
*/
public abstract class AlertHandler {
protected AlertRule rule;
protected Notification notification;
public AlertHandler(AlertRule rule, Notification notification) {
this.rule = rule;
this.notification = notification;
}
public abstract void check(ApiInfo apiInfo);
}
/**
* @description: 接口请求出错数的告警处理器
* @version: 1.0
* @author:blackcat
*/
public class ErrorAlertHandler extends AlertHandler {
public ErrorAlertHandler(AlertRule rule, Notification notification) {
super(rule, notification);
}
@Override
public void check(ApiInfo apiInfo) {
String api = apiInfo.getApi();
long errorCount = apiInfo.getErrorCount();
//当接口请求出错数大于某个最大允许值时,发送通知
if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
notification.notify(NotificationEmergencyLevel.SEVERE, "接口请求出错数超过阈值发送严重预警");
}
}
}
/**
* @description: TPS的告警处理器
* @version: 1.0
* @author:blackcat
*/
public class TpsAlertHandler extends AlertHandler {
public TpsAlertHandler(AlertRule rule, Notification notification) {
super(rule, notification);
}
@Override
public void check(ApiInfo apiInfo) {
String api = apiInfo.getApi();
long requestCount = apiInfo.getRequestCount();
long durationOfSeconds = apiInfo.getDurationOfSeconds();
long tps = requestCount / durationOfSeconds;
//当接口的 TPS 超过某个预先设置的最大值时,发送通知
if (tps > rule.getMatchedRule(api).getMaxTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "TPS超过阈值发送紧急预警");
}
}
}
修改Alter
java
/**
* @description:告警功能实现
* @version: 1.0
* @author:blackcat
*/
public class Alert {
private List<AlertHandler> alertHandlers = new ArrayList<>();
public void addAlertHandler(AlertHandler alertHandler) {
this.alertHandlers.add(alertHandler);
}
public void check(ApiInfo apiInfo) {
// 遍历各种告警处理器
for (AlertHandler handler : alertHandlers) {
handler.check(apiInfo);
}
}
}
上层组装调用
java
public class ApplicationContext {
private AlertRule alertRule;
private Notification notification;
private Alert alert;
public void initializeBeans() {
alertRule = new AlertRule();
notification = new Notification();
alert = new Alert();
// 添加告警处理器
alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
}
// 饿汉式单例
private static final ApplicationContext instance = new ApplicationContext();
private ApplicationContext() {
instance.initializeBeans();
}
public static ApplicationContext getInstance() {
return instance;
}
}
新需求改造
1.引入添加新的处理器类TimeoutAlertHandler
java
/**
* @description: 接口请求出错数的告警处理器
* @version: 1.0
* @author:blackcat
*/
public class TimeoutAlertHandler extends AlertHandler {
public TimeoutAlertHandler(AlertRule rule, Notification notification) {
super(rule, notification);
}
@Override
public void check(ApiInfo apiInfo) {
String api = apiInfo.getApi();
long durationOfSeconds = apiInfo.getDurationOfSeconds();
//新增 当每秒钟接口超时请求个数超过阈值发送紧急预警"
long timeoutCount = apiInfo.getTimeoutCount();
long timeoutTps = timeoutCount / durationOfSeconds;
if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "当每秒钟接口超时请求个数超过阈值发送紧急预警");
}
}
}
ApplicationContext
新建TimeoutAlertHandler
java
```java
public class ApplicationContext {
public void initializeBeans() {
alertRule = new AlertRule();
notification = new Notification();
alert = new Alert();
// 添加告警处理器
alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
alert.addAlertHandler(new TimeoutAlertHandler(alertRule, notification));
}
}
3.ApiInfo 属性timeoutCount 和Rule的属性和初始化
总结
可能你会纠结我也明明修改代码了,怎么就是对修改关闭了呢?
第一个修改的地方是向 ApiInfo
类中添加新的属性 timeoutCount
。实际上,开闭原则可以应用在不同粒度的代码中,可以是模块,也可以类,还可以是方法(及其属性)。同样一个代码改动,在粗代码粒度下,被认定为"修改",在细代码粒度下,又可以被认定为"扩展"。比如这里的添加属性和方法相当于修改类,在类这个层面,这个代码改动可以被认定为"修改";但这个代码改动并没有修改已有的属性和方法,在方法(及其属性)这一层面,它又可以被认定为"扩展"。
另外一个修改的地方是在 ApplicationContext
类的 initializeBeans()
方法中,往 alert
对象中注册新的 timeoutAlertHandler
;在使用 Alert
类的时候,需要给check()
函数的入参 apiInfo
对象设置 timeoutCount
的值。首先说明添加一个新功能,不可能任何模块、类、方法的代码都不"修改",这个是不可能的。主要看修改的是什么内容,这里的修改是上层的代码,而非核心下层的代码,所以是可以接受的。