扩展性是衡量代码质量的重要标准,而在23种经典设计模式当中,大部分都是为了维护扩展性而存在的,而开闭原则,也就是在追求扩展性的同时,避免影响到代码的可读性.

如何理解开闭原则,详细描述一下,就是 诸如软件实体(类,方法,模块)应该对扩展开放,对修改关闭

也就是,添加一个新的功能的时候,应该是在已有的代码(新增类,方法,模块)基础上扩展代码,而不是修改已有的代码(修改已有的类,方法,模块)

接下来我们拿一个API接口监控告警的代码来举例说明

public class Alert {

private AlertRule rule;

private Notification notification;

public Alert(AlertRule rule, Notification notification) {

this.rule = rule;

this.notification = notification;

}

public void check(String api, long requestCount, long errorCount, long durationOfSeconds) {

long tps = requestCount / durationOfSeconds;

if (tps > rule.getMatchedRule(api).getMaxTps()) {

notification.notify(NotificationEmergencyLevel.URGENCY, “…”);

}

if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {

notification.notify(NotificationEmergencyLevel.SEVERE, “…”);

}

}

}

上面包含了告警通知类,支持告警事件通知,NotificationEmergencyLevel常量类,则是表示紧急程度,主要的函数check(),则是在TPS大于某个值,或者接口请求出错大于某个值的时候,触发告警

如果需要在上面的逻辑中新增一个逻辑,检测超时时间呢?主要改动有两处,一是修改check()函数的入参,二是,修改check()函数的主体逻辑

public class Alert {

// …省略AlertRule/Notification属性和构造函数…

// 改动一:添加参数timeoutCount

public void check(String api, long requestCount, long errorCount, long timeoutCount, long durationOfSeconds) {

long tps = requestCount / durationOfSeconds;

if (tps > rule.getMatchedRule(api).getMaxTps()) {

notification.notify(NotificationEmergencyLevel.URGENCY, “…”);

}

if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {

notification.notify(NotificationEmergencyLevel.SEVERE, “…”);

}

// 改动二:添加接口超时处理逻辑

long timeoutTps = timeoutCount / durationOfSeconds;

if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {

notification.notify(NotificationEmergencyLevel.URGENCY, “…”);

}

}

}

这样,所以对于这个接口的单元测试都需要修改,调用接口的代码也要修改,那么,如果遵循开闭原则来重构代码呢?

我们将多个入参的属性封装为一个包装类ApiStaiInfo类

引入handle概念,将if判断分散出去,避免耦合

public class Alert {

private List<AlertHandler> alertHandlers = new ArrayList<>();

public void addAlertHandler(AlertHandler alertHandler) {

this.alertHandlers.add(alertHandler);

}

public void check(ApiStatInfo apiStatInfo) {

for (AlertHandler handler : alertHandlers) {

handler.check(apiStatInfo);

}

}

}

public class ApiStatInfo {//省略constructor/getter/setter方法

private String api;

private long requestCount;

private long errorCount;

private long durationOfSeconds;

}

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(ApiStatInfo apiStatInfo);

}

public class TpsAlertHandler extends AlertHandler {

public TpsAlertHandler(AlertRule rule, Notification notification) {

super(rule, notification);

}

@Override

public void check(ApiStatInfo apiStatInfo) {

long tps = apiStatInfo.getRequestCount()/ apiStatInfo.getDurationOfSeconds();

if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) {

notification.notify(NotificationEmergencyLevel.URGENCY, “…”);

}

}

}

public class ErrorAlertHandler extends AlertHandler {

public ErrorAlertHandler(AlertRule rule, Notification notification){

super(rule, notification);

}

@Override

public void check(ApiStatInfo apiStatInfo) {

if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {

notification.notify(NotificationEmergencyLevel.SEVERE, “…”);

}

}

}

//具体的使用为

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));

}

public Alert getAlert() { return alert; }

// 饿汉式单例

private static final ApplicationContext instance = new ApplicationContext();

private ApplicationContext() {

instance.initializeBeans();

}

public static ApplicationContext getInstance() {

return instance;

}

}

public class Demo {

public static void main(String[] args) {

ApiStatInfo apiStatInfo = new ApiStatInfo();

// …省略设置apiStatInfo数据值的代码

ApplicationContext.getInstance().getAlert().check(apiStatInfo);

}

}

上面的ApplicationContext是一个基于面向对象思想开发的类,主要功能是组装alert类,然后暴露出来

那么添加新的需求就很简单了

首先,在ApiStatInfo类中添加新的属性

然后添加新的TimeoutAlertHandler类

在ApplicationContext类中的initializeBeans()中,加入新的Handler类

使用的时候别忘设置ApiStatInfo的值

重构后的代码更加的灵活,如果需要添加新的告警逻辑,基于扩展的方式创建新的Handler即可,原来的check()函数不变,单元测试不变

这种修改代码的方式符合开闭原则吗

那我们看下上面的改动

改动1,往ApiStatInfo类中添加新的属性timeoutCount

这个增加了属性和相对应的getter/setter

对于这个操作,从类的角度来说,是进行了修改,但是从方法的角度来说,是进行了扩展

可以说,只要不破坏原有的代码的正常运行,没有破坏原有的单元测试,就是合格的代码改动

但剩下的修改,虽然是在方法还是类层面上都是修改,但是这种修改是符合开闭原则的,因为新增一个功能,不可能不修改任何的类,我们只需要保证在不破坏其他代码调用的情况下修改即可

如何保证对扩展开放,对修改关闭呢?

我们通过引入一组handler来支持开闭原则.这种设计思路很巧妙,那么如何才能再编写的时候做到这么好的代码呢?主要就是靠着是长期的开发经验

在写之前,先多思考一下,这段代码可能未来具有什么需求变更,是否设计代码,实现留有预留点,方便去改动,而且记性合理的抽象化.当老的实现需要变化的时候,只需要基于相同的抽象接口,实现新的实现即可

而具体的提高代码的扩展性的方法有,多态,依赖注入,基于接口而非实现编程,大部分的设计模式,也是为了提高扩展性

对于很多项目,写出支持预留扩展点的代码是最好的,那么如何识别可能的扩展点呢?

如果是一个具体的业务,那么就需要考虑业务未来的可能扩展方向,如果是一个通用的,偏底层的系统,就需要考虑使用者如何使用

而,真正的开发中,我们对于一些不会进行变动的模块,也不需要去考虑预留扩展点,做过度设计

所以,需要我们针对真实的情况,进行灵活的应用原则

发表评论

邮箱地址不会被公开。 必填项已用*标注