我是蟬沐風,一個讓你沉迷于技術的講述者
微信公眾號【蟬沐風】,歡迎大家關注留言
時間都去哪兒了
「跑碼場」在陀螺的經營下,貓糧生意一直很紅火,
這一天,陀螺找到程式喵招財,說道:“年關將至,最近訂單有點多,我查看了一下系統監控,發現RT有點長,你排查一下原因,別影響顧客下單,”
“RT是個啥?”招財撓了撓頭問道,
“RT就是系統回應時間啊,在你進行系統升級之后,系統回應時間比原來變長了,”
“......直接說系統變卡了不就得了,還說得這么花里胡哨”,招財小聲嘀咕,卻也不敢直接回懟自己的師傅,
陀螺看著招財,“你這家伙在嘀咕什么呢?”,
“啊,沒有沒有,”招財連忙解釋道,“我在想,時間都去哪兒了呢?前段時間系統做了下升級,對接了一種新的支付方式,問題很有可能出在第三方的支付介面上,”
public interface Payable {
/**
* 支付介面
*/
void pay();
}
/**
* @author 蟬沐風
* @description 「四十大盜」金融公司
* @date 2022/1/5
*/
public class SiShiDaDao implements Payable {
@Override
public void pay() {
try {
// ...
System.out.println("「四十大盜」支付介面呼叫中......");
//模擬方法呼叫延時
TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 6000));
// ...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
“你這鍋倒是甩得挺快,那你說說怎么確定這個支付介面的執行時間呢?”陀螺問道,
“這還不簡單,我在呼叫pay()方法的位置前后各自添加一個記錄時間戳的陳述句就行了,就像這樣,”
public class Client {
public static void main(String[] args) {
Payable payable = new SiShiDaDao();
System.out.println("方法計時開始");
long startTime = System.currentTimeMillis();
payable.pay();
long endTime = System.currentTimeMillis();
System.out.println("方法運行時長為:" + (endTime - startTime) + "毫秒");
}
}

"師傅,你看吶!這個方法居然執行了3秒多,問題果然出在了這個介面身上!"招財臉上掛著抑制不住的興奮,
“你的直覺和運氣都很好,這么快就被你定位到了問題,”陀螺的表情未見波動,“先不著急修改,對于單純的排查測驗而言,你的代碼并沒有問題,但是如果現在恰好有一個在pay()方法執行前后添加記錄時間戳的業務需求,你會怎么實作?”
“我并不覺得上面寫的測驗代碼用在實際業務場景下會有什么問題,”招財自信地說道,
“如果還需要你在上述需求的邏輯前后再添加日志記錄呢?”陀螺追問,
“那就繼續在前后添加日志邏輯唄,”
“如果還需要你繼續添加支付權限審核邏輯以及積分變動邏輯呢?”陀螺繼續追問,
“這......雖然可以繼續在呼叫的位置前后追加各種邏輯,但是如此一來方法未免太臃腫了,但是真的會有這么變態的需求嗎?”招財已然沒有了之前的興奮,眼神里透著迷惘,
“需求這種東西,唯一不變的就是變化本身,”
靜態代理的誕生
“那有沒有什么好的解決辦法呢?”招財問道,
“我知道你現在住的房子是通過房產中介找到的,其實房產中介就是一個解決這個問題的思路,中介在你和真正的房屋出租者之間充當了媒介,中介對你提供的租房服務并不是中介本身有房子出租(排除中介自己買房子出租的情況),本質上是利用了房屋出租者提供的房屋出租功能,只不過中介加入了更強的宣傳推介,”陀螺解釋道,
“我還是沒懂,房產中介和這個第三方介面有什么聯系?”
陀螺繼續解釋道:“中介就是一個代理,代理本身使用了被代理物件提供的功能,但是又在功能的基礎上做了增強,再以支付介面為例,金融公司提供的支付介面就是被代理物件(相當于真正的房屋出租者),處于某種考慮(保護被代理物件,或者被代理物件已經邏輯完備,例如無法要求房屋出租者有中介那么強有力的推銷渠道),我們不會要求這個介面給我們提供更多的邏輯功能(因為是第三方jar包,我們無法修改原始碼),我們需要創造一個類似于房產中介的一個支付代理物件,在實作支付功能的基礎上加上我們需要的業務邏輯,”
“我明白了,這是一種設計模式嗎?”
“是的,這就是靜態代理模式,以記錄運行時間為例,嘗試著實作一下靜態代理模式吧,”陀螺鼓勵招財,
臃腫的繼承
招財思考了一會兒,寫出了如下代碼
/**
* @author 蟬沐風
* @description 「四十大盜」金融公司計時功能的代理
* @date 2022/1/5
*/
public class SiShiDaDaoTimeProxy extends SiShiDaDao {
@Override
public void pay() {
System.out.println("方法計時開始");
long startTime = System.currentTimeMillis();
super.pay();
long endTime = System.currentTimeMillis();
System.out.println("方法運行時長為:" + (endTime - startTime) + "毫秒");
}
}
“我創建了一個繼承自SiShiDaDao的代理物件SiShiDaDaoTimeProxy,并重寫了pay()方法,在呼叫父類pay()方法的基礎上,前后添加了計時的邏輯,這就是你說的功能增強吧,如此一來,客戶端呼叫支付介面的時候表面上使用的是我寫的代理物件,但是本質上用的還是金融公司的介面,”
說罷,招財又寫出了客戶端呼叫的代碼,
/**
* @author chanmufeng
* @description 呼叫客戶端
* @date 2022/1/5
*/
public class Client {
public static void main(String[] args) {
SiShiDaDaoTimeProxy proxy = new SiShiDaDaoTimeProxy();
proxy.pay();
}
}

陀螺欣慰地點點頭,"很好,你已經理解了靜態代理的本質,如果我現在要你在開始計時之前列印一條日志,在計時結束之后再列印一條日志,對你來說也不是什么難事兒了,"
“簡單!看我的!”招財很快便寫出了代碼,
/**
* @author 蟬沐風
* @description 「四十大盜」金融公司日志計時代理
* @date 2022/1/5
*/
public class SiShiDaDaoLogTimeProxy extends SiShiDaDaoTimeProxy {
@Override
public void pay() {
System.out.println("列印日志1");
super.pay();
System.out.println("列印日志2");
}
}
客戶端代碼和運行結果如下
public class Client {
public static void main(String[] args) {
SiShiDaDaoLogTimeProxy proxy = new SiShiDaDaoLogTimeProxy();
proxy.pay();
}
}

陀螺盯著招財,極力憋住笑聲,繼續問道,“我現在后悔了,想先計時,然后再列印日志,你該怎么辦?”
招財慌了,好家伙,剛教育我的唯一不變的就是變化這個真理這么快就讓我付諸實踐了,
招財想,需求雖然只是變化了一下邏輯順序,但是對于我實作而言簡直就是翻天覆地的變化,為了應對需求,我必須先創建一個繼承自SiShiDaDao的代理物件SiShiDaDaoLogProxy,然后再創建一個SiShiDaDaoTimeLogProxy繼承SiShiDaDaoLogProxy,這還只是兩層邏輯,萬一邏輯更多,需要修改的代價就太大了!
招財明白了,這是陀螺故意考驗自己,
“師傅,您就別玩兒我了,我意識到了我目前的實作方式不足以靈活地應付您說的需求,可是問題究竟出在哪兒呢?”招財求饒道,
陀螺笑著說:“看來你終于發現問題了,你使用繼承實作了靜態代理,可以達到目的,但是不夠靈活,看一下使用繼承時的UML類圖,”

面向介面編程
招財看了一下,果然發現了問題,使用繼承得到的UML是一條筆直的邏輯鏈,毫無復用性可言,無法通過組合的方式來滿足不同的邏輯呼叫順序,
哎?組合?招財想到了什么,“我好像知道如何走出這個困境了,看我代碼”,
/**
* @author 蟬沐風
* @description 「四十大盜」金融公司計時代理
* @date 2022/1/6
*/
public class SiShiDaDaoTimeProxy implements Payable {
//被代理物件
private Payable payable;
public SiShiDaDaoTimeProxy(Payable payable) {
this.payable = payable;
}
@Override
public void pay() {
System.out.println("方法計時開始");
long startTime = System.currentTimeMillis();
payable.pay();
long endTime = System.currentTimeMillis();
System.out.println("方法運行時長為:" + (endTime - startTime) + "毫秒");
}
}
/**
* @author 蟬沐風
* @description 「四十大盜」金融公司日志代理
* @date 2022/1/6
*/
public class SiShiDaDaoLogProxy implements Payable {
//被代理物件
private Payable payable;
public SiShiDaDaoLogProxy(Payable payable) {
this.payable = payable;
}
@Override
public void pay() {
System.out.println("列印日志1");
payable.pay();
System.out.println("列印日志2");
}
}
"我使用組合的方式來代替繼承,計時代理和日志代理都實作了Payable介面,在創建代理的同時需要傳入被代理物件,然后在代理中呼叫傳入的被代理物件的方法,在方法前后就可以做一些增強的操作了,"招財解釋道,
“接著說說,用這種實作方式是怎么解決我剛才的問題的?”陀螺繼續問道,
招財不慌不忙,“如果現在的需求是先列印日志,再計算時間,客戶端只需要這么呼叫,”
public class Client {
public static void main(String[] args) {
//先列印日志,再計算時間
Payable proxy = new SiShiDaDaoLogProxy(new SiShiDaDaoTimeProxy(new SiShiDaDao()));
proxy.pay();
}
}

"同樣,如果想先計算時間,再列印日志,只需要修改一下代理生成的順序就可以了,至于代理的內部實作一點也不需要變動,因為每個代理本身實作了Payable,因此又可以作為被代理物件傳入,繼續被其他物件所代理,"
public class Client {
public static void main(String[] args) {
//先計算時間,再列印日志
Payable proxy = new SiShiDaDaoTimeProxy(new SiShiDaDaoLogProxy(new SiShiDaDao()));
proxy.pay();
}
}
“有點俄羅斯套娃的意思了”,陀螺聽著招財這么解釋,笑著說道,
“這個說法還真是形象,根據需求調整“套”的順序就可以了,UML圖我也給出來了,”

“非常好,目前為止你已經解決了時間都去哪兒了的問題了,如果現在我讓你在訂單系統的所有方法前后都添加計時功能和日志功能怎么辦?而且我可能還想僅針對對某些類中的某些方法執行前后添加計時功能和日志功能,這該怎么辦?我還想......”,
招財趕緊打斷了陀螺,“......師傅,趕緊打住!您一旦這么問,就說明我目前的實作指定是滿足不了您的需求了,等我先回去想想吧,目前的當務之急是趕緊跟金融公司提個pr,修復一下這個問題,別影響顧客下單,”
“我看你是怕影響你的年終獎”,陀螺嗔怪道,
“哈哈哈哈哈哈,不說了不說了,我pr去了”,招財趕緊一溜煙跑沒了影,
很快,金融公司修復了這個問題,訂單系統穩定如初,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/404305.html
標籤:其他
上一篇:靜態代理模式——時間都去哪兒了
下一篇:計算機網路名詞縮寫
