麵包店裡的程式設計狂想

java填坑路發表於2018-08-04

小黑最近手頭有點緊,於是想著去麵包店裡兼職賺點外快。剛剛到麵包店裡上班就看到麵包師傅在做蛋糕和麵包,但是都是手工一個個在做,半個小時才能做出一個蛋糕。程式設計師出身的小黑就不由自主地思考起來,要是能開發出一臺機器,它能自動做蛋糕,那該多好啊。10分鐘就能做出一個蛋糕,10分鐘就能做好一批麵包,這樣效率又高,又可以減掉人力成本。我們用 Java 語言描述一下這個場景:

小黑把這個想法告訴了店長,店長說:不錯,小夥子很有想法。我會嘗試著去做一做,要是做出來了肯定能大大提高生產效率!不知道過了多久,小黑發現店裡竟然真的用機器來做蛋糕和麵包了。

但生活中的場景往往是複雜多變的。就在小黑驚訝的時候,店裡來了一個顧客,他想要一個水果蛋糕,但他特別喜歡杏仁,希望在水果蛋糕上加上一層杏仁。這時候我們應該怎麼做呢?

最簡單的辦法是直接修改水果蛋糕機的程式,做一臺能做杏仁水果蛋糕的蛋糕機。這種方式對應的程式碼修改也很簡單,直接在原來的程式碼上進行修改,生成一臺專門做杏仁水果蛋糕的機器就好了,修改後的 FruitCakeMachien 類應該是這樣子:

雖然上面這種方式實現了我們的業務需求。但是仔細想一想,在現實生活中如果我們遇到這樣的一個需求,我們不可能因為一個顧客的特殊需求就去修改一臺蛋糕機的硬體程式,這樣成本太高!而且從程式碼實現角度上來說,這種方式從程式碼上不是很優雅,修改了原來的程式碼。根據「對修改封閉、對擴充套件開放」的思想,我們在嘗試滿足新的業務需求的時候應該儘量少修改原來的程式碼,而是在原來的程式碼上進行擴充。

那我們究竟應該怎麼做更加合適一些呢?我們肯定是直接用水果蛋糕機做一個蛋糕,然後再人工撒上一層杏仁啦。我們需要做的,其實就是設計一個杏仁代理類(ApricotCakeProxy),這個代理類就完成撒杏仁這個動作,之後讓蛋糕店直接呼叫即可代理類去實現即可。

這其實就對應了即使模式中的代理模式,雖然呼叫的是 ApricotCakeProxy 類的方法,但實際上真正做蛋糕的是 FruitCakeMachine 類。ApricotCakeProxy 類只是在 FruitCakeMachine 做出蛋糕後,撒上一層杏仁而已。而且通過代理,我們不僅可以給水果蛋糕撒上一層杏仁,還可以給巧克力蛋糕、五仁蛋糕等撒上一層杏仁。只要它是蛋糕(實現了 CakeMachine 介面),那麼我們就可以給這個蛋糕撒上杏仁。

通過代理實現這樣的業務場景,這樣我們就不需要在原來的類上進行修改,從而使得程式碼更加優雅,擴充性更強。如果下次客人喜歡葡萄乾水果蛋糕了了,那可以再寫一個 CurrantCakeProxy 類來撒上一層葡萄乾,原來的程式碼也不會被修改。上面說的這種業務場景就是代理模式的實際應用,準確地說這種是靜態代理。

業務場景的複雜度往往千變萬化,如果這個特別喜歡杏仁的客人,他也想在麵包上撒一層杏仁,那我們怎麼辦?我們能夠使用之前寫的 ApricotCakeProxy 代理類麼?不行,因為 ApricotCakeProxy 裡規定了只能為蛋糕(實現了 CakeMachine 介面)的實體做代理。這種情況下,我們只能再寫一個可以為所有面包加杏仁的代理類:ApricotBreadProxy。

我們可以看到我們也成功地做出了客人想要的杏仁紅豆麵包、杏仁葡萄乾麵包。對於客人來說,他肯定希望我們所有的產品都有一層杏仁,這樣客人最喜歡了。為了滿足客人的需求,那如果我們的產品有 100 種(餅乾、酸奶等),我們是不是得寫 100 個代理類呢?

有沒有一種方式可以讓我們只寫一次實現(撒杏仁的實現),但是任何型別的產品(蛋糕、麵包、餅乾、酸奶等)都可以使用呢?其實在 Java 中早已經有了針對這種情況而設計的一個介面,專門用來解決類似的問題,它就是動態代理 —— InvocationHandler。

接下來我們針對這個業務場景做一個程式碼的抽象實現。首先我們分析一下可以知道這種場景的共同點是希望在所有產品上都做「撒一層杏仁」的動作,所以我們就做一個杏仁動態代理(ApricotHandler)。

撒杏仁的代理寫完之後,我們直接讓蛋糕店開工:

public class CakeShop {

public static void main(String[] args) {

//動態代理(可以同時給蛋糕、麵包等加杏仁)

//給蛋糕加上杏仁

FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();

ApricotHandler apricotHandler = new ApricotHandler(fruitCakeMachine);

CakeMachine cakeMachine = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),

fruitCakeMachine.getClass().getInterfaces(),

apricotHandler);

cakeMachine.makeCake();

//給麵包加上杏仁

RedBeanBreadMachine redBeanBreadMachine = new RedBeanBreadMachine();

apricotHandler = new ApricotHandler(redBeanBreadMachine);

BreadMachine breadMachine = (BreadMachine) Proxy.newProxyInstance(redBeanBreadMachine.getClass().getClassLoader(),

redBeanBreadMachine.getClass().getInterfaces(),

apricotHandler);

breadMachine.makeBread();

}

}

與靜態代理相比,動態代理具有更加的普適性,能減少更多重複的程式碼。試想這個場景如果使用靜態代理的話,我們需要對每一種型別的蛋糕機都寫一個代理類(ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy 等)。但是如果使用動態代理的話,我們只需要寫一個通用的撒杏仁代理類(ApricotHandler)就可以直接完成所有操作了。直接省去了寫 ApricotCakeProxy、ApricotBreadProxy、ApricotCookieProxy 的功夫,極大地提高了效率。

簡單地說,靜態代理只能針對特定一種產品(蛋糕、麵包、餅乾、酸奶)做某種代理動作(撒杏仁),而動態代理則可以對所有型別產品(蛋糕、麵包、餅乾、酸奶等)做某種代理動作(撒杏仁)。

小黑表示一切程式設計思想都可以從現實生活中找到合適的例子,Java 不愧是經典的面嚮物件語言。看到這裡,你們理解靜態代理和動態代理的區別了嗎?歡迎留言告訴我。

歡迎工作一到五年的Java工程師朋友們加入Java架構開發:744677563

本群提供免費的學習指導 架構資料 以及免費的解答

不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導


相關文章