1. 程式人生 > >面包店裏的編程狂想

面包店裏的編程狂想

tco 分析 mach ima 巧克力 多好 動作 自主 rgs

小黑最近手頭有點緊,於是想著去面包店裏兼職賺點外快。剛剛到面包店裏上班就看到面包師傅在做蛋糕和面包,但是都是手工一個個在做,半個小時才能做出一個蛋糕。程序員出身的小黑就不由自主地思考起來,要是能開發出一臺機器,它能自動做蛋糕,那該多好啊。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 不愧是經典的面向對象語言。看到這裏,你們理解靜態代理和動態代理的區別了嗎?歡迎留言告訴我。

面包店裏的編程狂想