設計模式——適配器模式和裝飾者模式
- 2020 年 3 月 4 日
- 筆記
寫在之前
公眾號感覺很久沒有更新啦,首先謝謝大家的支援哈哈,好友問我最近公眾號怎麼不更新了,才意識到自己變懶啦,對不起大家的支援和鼓勵,所以趕緊補補,以後也希望自己繼續努力,堅持下去,也感謝大家一如既往的支援
正文

適配器模式
網上有很多適配器模式的定義和講解,這裡我就記錄下自己對適配器模式的理解,更多的大家可以在網上看。
適配器模式到底是什麼,也就是所謂的定義:
個人理解,適配適配當然是之前不合適,通過一些手段從而合適了,這些手段就是適配。比方說你買了一個港版的iPhone,但是你現在在內地,怎麼對iPhone充電呢。這個時候你需要通過轉介面達到充電的目的。而這個轉介面就是所謂的適配器。
你想繼承或實現類A,但是有一些方法在類B中實現。你想用類B的方法,但是又不能直接實現或者繼承,這個時候就需要一個適配器(抽象類)來繼承或實現類B中想使用的方法,然後類B繼承這個抽象類。
適配器模式的一個簡單的例子:
目錄

En110v.java
package test; /** * 香港充電方式 */ public interface En110v { void enCharging(String address); }
Cn220v.java
package test; /** * 內地充電方式 */ public interface Cn220v { void cnCharging(String address); }
IphoneCharging.java
package test; import org.apache.commons.collections.bag.SynchronizedSortedBag; /** * @ClassName IphoneCharging * @Description 港版iPhone充電 */ public class IphoneCharging implements En110v{ @Override public void enCharging(String address) { System.out.println(address+",港版手機在充電中。"); } } /** * 那想要在內地充電,怎麼辦呢. * 我們不能直接把插頭插在220v的插座上,正如我們不能直接實現Cn220v這個類一樣 * 所以我們需要一個轉換頭來達到目的 * 也就是這裡需要適配器類Adapter */
Adapter.java
package test; /** * @ClassName Adapter * @Description 適配器 */ public class Adapter extends IphoneCharging implements Cn220v{ @Override public void cnCharging(String address) { enCharging(address); System.out.println("使用適配器"); } } /** * 這個適配器就是在進行內地充電的轉成港版充電 * 也就是表面上執行A方法,實際上執行的是B方法 */
Charging.java
package test; /** * @ClassName Charging * @Description 充電 */ public class Charging { private static final String MAINLAND="內地"; private static final String HONGKONG="香港"; public static void main(String[] args) { //香港充電,直接創建IponeChraging實例就可以 IphoneCharging hongkong=new IphoneCharging(); hongkong.enCharging(HONGKONG); //在內地充電,沒有可以直接內地充電的實例,所以只能用轉接頭 Adapter adapter=new Adapter(); adapter.cnCharging(MAINLAND); } } /** * 特別說明 * 在中國充電只能用cnCharging方式, * 在香港充電只能用enCharging方式,這個是前提。 * 不然直接 adapter.enCharging(MAINLAND);程式不抱錯,但是不符合實情了。 */
結果:
香港,港版手機在充電中。 內地,港版手機在充電中。 使用適配器
可能這個例子不太好,但是也剛好我們理解一下適配器模式吧,將一個類的介面轉換成客戶希望的另外一個介面。適配器模式使得原本由於介面不兼容而不能一起工作的那些類可以一起工作。
應用場景:
主要解決在軟體系統中,常常要將一些"現存的對象"放到新的環境中,而新環境要求的介面是現對象不能滿足的。核心思想:適配器繼承或依賴已有的對象,實現想要的目標介面。
優點:
1、可以讓任何兩個沒有關聯的類一起運行。
2、提高了類的復用。
3、增加了類的透明度。
4、靈活性好。
缺點:
1、過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 介面,其實內部被適配成了 B 介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
2.由於 JAVA 至多繼承一個類,所以至多只能適配一個適配者類,而且目標類必須是抽象類。
裝飾者模式
裝飾裝飾當然是一個東西還能用,但是呢達不到理想的效果想要更好一點。所以就在原來的基礎上進行裝飾從而達到目的,這就是裝飾者模式啦,和適配器區別在於,適配器模式是原來的東西在現在的環境不能使用,為了達到使用的效果需要使用適配器才能使用。而裝飾者模式是原來的東西在現在的環境還能用,但是呢,你想新增一些特性,所以就需要對原來的東西進行裝飾,達到想要的效果。
裝飾者模式:就是動態地把職責附加到已有對象上去,實現功能擴展。這種特性,使得裝飾者模式提供了比繼承更具有彈性的解決方案。
下面舉一個比較通俗的例子:
送你一個女朋友怎麼樣!想她是美國金髮大妞?浪漫的法國女郎?國產的萌萌噠妹子?OK,沒問題!你想她是哪個國家的就是哪個國家的。她們有不同的愛好或者習慣,每一個這樣的女孩,都可以看作是一個 Java 類。我知道此刻你一定在想,這一個、那一個…那豈不是有很多類?這種方式沒有擴展性,每當有一個新類型的女孩,就得又新建一個類,這簡直就是類爆炸啊!
所以這個時候就可以用到裝飾者模式,先講一些共有的特性提取出來形成積累,然後需要一個裝飾者的抽象類,來繼承基類,其他的不同特性每個特性都自成一個類來繼承這個裝飾者的類。從而達到通過不同的裝飾達到不用的效果,就像房子通過不同的裝修達到不同的效果。
下面來看下程式碼:
整體的目錄:

Girl.java
package cn.zlf.code.decoratormode; /** * 抽象類 girl ,作為基類 */ public abstract class Girl { String description; public String getDescription() { return description; } }
EnGirl.java
package cn.zlf.code.decoratormode; /** * 外國女孩的類 */ public class EnGirl extends Girl{ private static final String ENGIRL="我是外國女孩,"; public EnGirl(){ description=ENGIRL; } }
CnGirl.java
package cn.zlf.code.decoratormode; /** *國產妹子 */ public class CnGirl extends Girl{ private static final String CNGIRL="我是國產妹子,"; public CnGirl(){ description=CNGIRL; } }
GirlDecorator.java
package cn.zlf.code.decoratormode; /** * 裝飾者 */ public abstract class GirlDecorator extends Girl{ public abstract String getDescription(); } /** * 好了,接下來我們想要什麼樣的女朋友呢 * 比如說:國產黑色長髮妹子。 * 那我們現在該怎樣使用這個裝飾者呢 * 需要一個BlackLongHairGirl 來集成這個裝飾者 */
BlackLongHairGirl.java
package cn.zlf.code.decoratormode; /** * 國產黑色長髮妹子 */ public class BlackLongHairGirl extends GirlDecorator{ private Girl girl; private static final String BLACKLongHair="黑色長髮,"; public BlackLongHairGirl(Girl girl){ this.girl=girl; } @Override public String getDescription() { return girl.getDescription()+BLACKLongHair; } } /** * 上面黑色和長發按正常的實際上是不能放一起的,畢竟黑色的一定是長發。 * 所以在實際開發時 * 要注意充分考慮程式碼的可重用性和拓展性 * 這裡就暫時不處理了,我們再寫一個類來表示一個新特徵 * 比如說想要一個黑色長髮大長腿的女朋友 * 現在已經有黑色長髮了 * 所以還需要一個大長腿 * 所以需要創建一個BigLongLegsGirl */
BigLongLegsGirl.java
package cn.zlf.code.decoratormode; /** * 大長腿女孩 * 繼承裝飾者,這裡為什麼不直接繼承BlackLongHairGirl呢 * 就是考慮程式碼的拓展性,萬一下次想要個短髮長腿的不就炸了 * 相當於把不同的屬性徹底分開降低耦合 */ public class BigLongLegsGirl extends GirlDecorator{ private Girl girl; private static final String BIGLONGLEGS="大長腿,"; public BigLongLegsGirl(Girl girl){ this.girl=girl; } @Override public String getDescription() { return girl.getDescription()+BIGLONGLEGS; } } /** * 好了,接下來寫一個類來創建你想要的女朋友吧 * CreateGirlfriend */
CreateGirlfriend.java
package cn.zlf.code.decoratormode; /** * 創建你想要的女朋友 */ public class CreateGirlfriend { public static void main(String[] args) { //想要一個國產妹子 Girl g1=new CnGirl(); System.out.println(g1.getDescription()); //現在想要一個國產黑色長髮妹子 Girl g2=new BlackLongHairGirl(g1); System.out.println(g2.getDescription()); //現在想要一個國產黑色長髮大長腿的妹子 Girl g3=new BigLongLegsGirl(g2); System.out.println(g3.getDescription()); System.out.println("----------------"); //那如果我想創建一個國外黑色長髮大長腿的女朋友呢,一步到位 Girl g4=new BigLongLegsGirl(new BlackLongHairGirl(new EnGirl())); System.out.println(g4.getDescription()); } }
結果:
我是國產妹子, 我是國產妹子,黑色長髮, 我是國產妹子,黑色長髮,大長腿, ---------------- 我是外國女孩,黑色長髮,大長腿,
上面這個例子就簡單的用到了裝飾者模式啦,是不是感覺很熟悉其實,在我們平時工作寫程式碼的時候其實這種模式經常用到,只是自己不自知,或者用的不是太規範罷了。所以我們以後工作寫程式碼的時候不妨也按這種思路設計,多想想以後的拓展性和可變性。不然好不容做出一個產品,結果因為每個客戶有些特定的需求又得做一個相似的產品就很費時費力了。所以當你需要動態地給一個對象添加功能,實現功能擴展的時候,就可以使用裝飾者模式。在Java IO 類中有一個經典的裝飾者模式應用, BufferedReader 裝飾了 InputStreamReader.
不過裝飾者模式缺點也很明顯,看上面的例子就可以看出來,才一個這麼簡單的例子就有好幾個類,每一個特性都單獨的一個類,這樣必然會造成很多相似的程式碼,且讓整個項目顯得很臃腫,所以到底使用那種模式還是仁者見仁智者見智了,畢竟沒有什麼是絕對的,最合適的就是最好的。
最後總結一下適配器模式和裝飾者模式的區別:
關於新職責:適配器也可以在轉換時增加新的職責,但其主要目的並不在此;而裝飾者模式主要目的,就是給被裝飾者增加新職責用的。
關於原介面:適配器模式是用新介面來調用原介面,原介面對新系統來說是不可見或者說不可用的;而裝飾者模式原封不動的使用原介面,系統對裝飾的對象也通過原介面來完成使用。
關於其包裹的對象:適配器是知道被適配者的詳細情況的(就是那個類或那個介面);而裝飾者只知道其介面是什麼,至於其具體類型(是基類還是其他派生類)只有在運行期間才知道。