騰訊大佬總結的程式碼重構原則,看完再也不怕面試官問啦!
前言
上一篇開閉原則最有用的程式碼改動是基於 “修改” 的方式來實現新功能的。如果我們遵循開閉原則,也就是 “對擴充套件開放、對修改關閉”。那如何通過 “擴充套件” 的方式,來實現同樣的功能呢?
重構
我們先重構一下之前的 Alert 程式碼,讓它的擴充套件性更好一些。重構的內容主要包含兩部分:
-
第一部分是將 check () 函式的多個入參封裝成 ApiStatInfo 類;
-
第二部分是引入 handler 的概念,將 if 判斷邏輯分散在各個 handler 中。
具體的程式碼實現如下所示:
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, "...");
}
}
}
上面的程式碼是對 Alert 的重構,我們再來看下,重構之後的 Alert 該如何使用呢?具體的使用程式碼我也寫在這裡了。
其中,ApplicationContext 是一個單例類,負責 Alert 的建立、組裝(alertRule 和 notification 的依賴注入)、初始化(新增 handlers)工作。
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); } }
現在,我們再來看下,基於重構之後的程式碼,如果再新增上面講到的那個新功能,每秒鐘介面超時請求個數超過某個最大閾值就告警,我們又該如何改動程式碼呢?主要的改動有下面四處。
- 第一處改動是:在 ApiStatInfo 類中新增新的屬性 timeoutCount。
- 第二處改動是:新增新的 TimeoutAlertHander 類。
- 第三處改動是:在 ApplicationContext 類的 initializeBeans () 方法中,往 alert 物件中註冊新的 timeoutAlertHandler。
- 第四處改動是:在使用 Alert 類的時候,需要給 check () 函式的入參 apiStatInfo 物件設定 timeoutCount 的值。
改動之後的程式碼如下所示:
public class Alert { // 程式碼未改動... }
public class ApiStatInfo {// 省略 constructor/getter/setter 方法
private String api;
private long requestCount;
private long errorCount;
private long durationOfSeconds;
private long timeoutCount; // 改動一:新增新欄位
}
public abstract class AlertHandler { // 程式碼未改動... }
public class TpsAlertHandler extends AlertHandler {// 程式碼未改動...}
public class ErrorAlertHandler extends AlertHandler {// 程式碼未改動...}
// 改動二:新增新的 handler
public class TimeoutAlertHandler extends AlertHandler {// 省略程式碼...}
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));
// 改動三:註冊 handler
alert.addAlertHandler(new TimeoutAlertHandler(alertRule, notification));
}
//... 省略其他未改動程式碼...
}
public class Demo {
public static void main(String[] args) {
ApiStatInfo apiStatInfo = new ApiStatInfo();
//... 省略 apiStatInfo 的 set 欄位程式碼
apiStatInfo.setTimeoutCount(289); // 改動四:設定 tiemoutCount 值
ApplicationContext.getInstance().getAlert().check(apiStatInfo);
}
重構之後的程式碼更加靈活和易擴充套件。如果我們要想新增新的告警邏輯,只需要基於擴充套件的方式建立新的 handler 類即可,不需要改動原來的 check () 函式的邏輯。而且,我們只需要為新的 handler 類新增單元測試,老的單元測試都不會失敗,也不用修改。
重點回顧
如何理解 “對擴充套件開放、對修改關閉”?
新增一個新的功能,應該是通過在已有程式碼基礎上擴充套件程式碼(新增模組、類、方法、屬性等),而非修改已有程式碼(修改模組、類、方法、屬性等)的方式來完成。關於定義,我們有兩點要注意。第一點是,開閉原則並不是說完全杜絕修改,而是以最小的修改程式碼的代價來完成新功能的開發。第二點是,同樣的程式碼改動,在粗程式碼粒度下,可能被認定為 “修改”;在細程式碼粒度下,可能又被認定為 “擴充套件”。
更多原創閱讀:java實驗室