扩展性是衡量代码质量的重要标准,而在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来支持开闭原则.这种设计思路很巧妙,那么如何才能再编写的时候做到这么好的代码呢?主要就是靠着是长期的开发经验
在写之前,先多思考一下,这段代码可能未来具有什么需求变更,是否设计代码,实现留有预留点,方便去改动,而且记性合理的抽象化.当老的实现需要变化的时候,只需要基于相同的抽象接口,实现新的实现即可
而具体的提高代码的扩展性的方法有,多态,依赖注入,基于接口而非实现编程,大部分的设计模式,也是为了提高扩展性
对于很多项目,写出支持预留扩展点的代码是最好的,那么如何识别可能的扩展点呢?
如果是一个具体的业务,那么就需要考虑业务未来的可能扩展方向,如果是一个通用的,偏底层的系统,就需要考虑使用者如何使用
而,真正的开发中,我们对于一些不会进行变动的模块,也不需要去考虑预留扩展点,做过度设计
所以,需要我们针对真实的情况,进行灵活的应用原则