策略模式

2022-11-30 06:04:40

風帶著萬物全部飄散,可那飄散的不是萬物,而是我的青春

一個簡單的鴨子應用

使用簡單繼承的模式實現多個不同的鴨子實現類。

新需求 - 鴨子能飛

現在公司為了甩開其他競爭者,需要在應用中新增一個新需求,得讓鴨子飛起來。

如圖所示,只要在鴨子類中新增一個讓鴨子飛的方法即可。

可怕的事情發生了

在公司的應用演示會上,有很多橡皮鴨在螢幕上飛來飛去。

造成這種問題提出現的原因:並不是所有的鴨子都會飛。

提示:

  • 對程式碼所做的區域性修改,影響層面可不只是區域性(會飛的橡皮鴨)

  • 當涉及到程式碼的維護時,為了「複用」程式碼而使用繼承,結局並不完美

利用介面改造程式碼

fly()方法從超類中去取出來,放進一個Flyable介面,只有會飛的鴨子去實現這個飛的介面。

這種介面改造,確實可以實現目的。但是太笨了,完全不利於程式碼的複用與維護。如果專案後期需要你修改一下鴨子的飛行動作,那麼你是不是要去每一個鴨子的實現類裡進行修改。

Java介面不具有現實程式碼,所以繼承介面無法實現程式碼的複用。這意味著:無論什麼時候你需要修改其實現類的某一個行為,你都要往下追蹤到實現類中進行修改。

我們要做的應該是,花費較少的時間重做程式碼,而讓程式去做更酷的事情。

把問題歸零

上面問題出現的原因,就是新需求來的時候,我們改動了系統中的某些部分,導致系統其他部分受到了影響。

所以,要解決這個問題,就是要把這兩部分分開,各自封裝處理。

即:

設計原則:

找到應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起。

分開變化和不會變化的部分

找出應用中哪些是不變的,哪些是變化的。

不變的:swim()display() 基本上所有的鴨子都會

會變化的:fly()quack() 不同的鴨子叫聲和飛行動作不一樣

把這兩部分分開

設計鴨子的行為

好的程式碼是非常有彈性的,就是因為一開始程式碼沒有彈性,才走到現在這條路上的。

如果我們現在需要生產一個新的綠鴨頭範例,並指定特定「型別」的飛行行為給它。乾脆順便讓鴨子的行為可以動態地改變好了。換句話說,我們應該在鴨子類中包含設定行為的方法,這樣就可以在「執行時」動態地「改變」綠鴨頭的飛行行為。

要實現這個目標,就需要用到下面的設計原則:

針對介面程式設計,而不是針對實現程式設計。

  • 以往的做法:行為來自Duck超類的具體實現,或是繼承某個介面並由子類自行實現而來。這兩種做法都是依賴於「實現」,我們被實現繫結死死的,要更改行為,就要去實現類裡進行程式碼的修改。

  • 針對介面程式設計:Duck的子類將使用介面(FlyBehaviorQuackBehavior)所表示行為,所以實際的「實現」不會被綁死在鴨子的子類中。(換句話說,特定的具體行為編寫在實現了FlyBehaviorQuackBehavior的類中)。

    • 這樣做的好處,後面新增一些新的行為,就不會影響到之前的程式碼,只需要在介面上進行擴充套件即可。而且這種方式,鴨子類就不再需要知道行為的具體實現細節,只需要呼叫介面的方法即可。

整合鴨子的行為

關鍵在於,鴨子現在會將飛行和呱呱叫行為「委託」別人處理,而不是使用定義在Duck類或其子類的呱呱叫和飛行行為。

每隻鴨子都會參照實現QuackBehavior介面物件,鴨子物件不親自處理呱呱叫和飛行行為,而是委託給代理介面去處理。

封裝行為的大局觀

核心:不再把鴨子的行為說成是「一組行為」,現在開始把行為想成是「一族演演算法」。

「有一個」可能比「是一個」更好

每一個鴨子都有一個FlyBehavior和一個QuackBehavior,好將飛行和呱呱叫行為委託給它們代為處理。當你將兩個類結合起來使用,這就叫組合。

這種組合做法與「繼承」不同的地方在於,鴨子行為不是繼承來的,而是和適當的行為物件「組合」來的。

這就是第三個設計原則:

多用組合,少用繼承。

使用組合建立系統具有很大的彈性,不僅可將演演算法族封裝成類,更可以「在執行時動態地改變行為」,只要組合的行為物件符合正確的介面標準即可。

總結

這就是策略模式(Strategy Pattern),策略模式定義了演演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演演算法的變化獨立於使用演演算法的客戶。