一、基本介紹
結構型模式(Structural Pattern)關注如何將現有類或物件組織在一起形成更加強大的結構。分為兩種:1,類結構型模式:關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係;2,物件結構型模式:關心類與物件的組合,通過關聯關係使得在一個類中定義另一個類的例項物件,然後通過該物件呼叫其方法,更符合“合成複用原則”。
具體的結構型模式可分為:
- 介面卡模式(Adapter Pattern)
- 橋接模式(Bridge)
- 裝飾者模式(Decorator)
- 組合模式(Composite Pattern)
- 外觀模式(Facade)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy)
二、介面卡模式
1,基本介紹
介面卡模式(Adapter Pattern)將某個類的介面轉換成客戶端期望的另一個介面表示,主要的目的是相容性,讓原本介面不匹配不能一起工作的兩個類可以協同工作。其別名為包裝器(Wrapper)。
介面卡模式屬於結構型模式,主要分為三類:類介面卡模式、物件介面卡模式、介面介面卡模式。
2,工作原理
- 介面卡模式:將一個類的介面轉換成另一個種介面,讓原本介面不相容的類可以相容
- 從使用者的角度看不到被適配者,是解耦的
- 使用者呼叫介面卡轉化出來的目標介面方法,介面卡再呼叫被適配者的相關介面方法
- 使用者接收到反饋結果,感覺只是和目標介面互動
3,類介面卡模式
a)類介面卡模式介紹
Adapter類,通過繼承src類,實現dst類介面,完成src -> dst的適配。
b)類介面卡應用例項
需求
以生活中充電器為例,充電器本身相當於Adapter,220V交流電相當於src(即被適配者),我們的目標dst(即目標)是5V直流電
思路分析(類圖)
public class Voltage220V { public int output220v() { int src = 220; System.out.println("原始電壓=" + src + "伏"); return src; } } public interface IVoltage5V { public int output5v(); } public class VoltageAdapter extends Voltage220V implements IVoltage5V { @Override public int output5v() { int v220 = output220v(); return v220 / 44; } } public class Phone { public void chrging(IVoltage5V iVoltage5V) { if (iVoltage5V.output5v() == 5) { System.out.println("電壓5V,可以充電"); } else { System.out.println("電壓" + iVoltage5V.output5v() + "V,無法充電"); } } } public class Client { public static void main(String[] args) { System.out.println("==========類介面卡模式========="); Phone phone = new Phone(); phone.chrging(new VoltageAdapter()); } }
c)注意事項與細節
- 缺點:Java是單繼承機制,所以類介面卡需要繼承src,故而dst必須是介面,有一定侷限性
- src類的方法在Adapter中都會暴露出來,也增加了使用的成本
- 優點:由於其繼承了src類,所以它可以根據需求重寫src類的方法,使得Adapter的靈活性增強了
4,物件介面卡模式
a)物件介面卡模式介紹
基本思路和類的介面卡模式相同,只是將Adapter類做修改,不再繼承src類,而是持有src類的例項(聚合),以解決相容性的問題。即:持有src類,實現dst類介面,完成src -> dst的適配。
即根據“合成複用原則”,在系統中儘量使用關聯關係(聚合)來替代繼承關係。物件介面卡模式是介面卡模式中常用的一種
b)物件介面卡應用例項
需求
同類介面卡
思路分析(類圖)
除了VoltageAdapter類之外,其他的類與類介面卡相同
public class VoltageAdapter implements IVoltage5V { private Voltage220V voltage220V; public VoltageAdapter(Voltage220V voltage220V) { this.voltage220V = voltage220V; } @Override public int output5v() { int v220 = voltage220V.output220v(); return v220 / 44; } }
c)注意事項與細節
- 物件介面卡和類介面卡其實算是同一種思想,只不過實現方式不同。物件介面卡根據合成複用原則,使用聚合替代繼承,所以它解決了類介面卡必須繼承src的侷限性問題,也不在要求dst必須時介面。
- 使用成本更低,更靈活
5,介面介面卡模式
a)介面介面卡模式介紹
- 當不需要全部實現介面提供的方法時,可先設計一個抽象類實現介面,併為該介面中每個方法提供一個預設實現(空方法),那麼該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求。
- 適用於一個介面不想使用其他所有的方法的情況
b)介面介面卡模式例項
類圖
public interface InterfaceAdapter { public void m1(); public void m2(); public void m3(); public void m4(); } public abstract class AbsAdapter implements InterfaceAdapter { @Override public void m1() {} @Override public void m2() {} @Override public void m3() {} @Override public void m4() {} } public class Client { public static void main(String[] args) { AbsAdapter absAdapter = new AbsAdapter() { @Override public void m1() { System.out.println("test absAdapter m1()"); } }; absAdapter.m1(); } }
6,介面卡模式的注意事項和細節
- 三種命名方式,是根據src是以怎樣的形式給到Adapter(在Adapter裡的形式)來命名的。
- 類介面卡:以類給到Adapter裡,也就是將src當做類,繼承
- 物件介面卡:以物件給到Adapter裡,也就是將src作為一個物件,持有
- 介面介面卡:以介面給到Adapter裡,也就是將src作為一個介面,實現
- Adapter模式最大的作用還是將原本不相容的介面融合在一起工作
三、橋接模式
1,手機操作問題
現在對不同手機型別的不同品牌實現操作程式設計(比如:開機、關機、上網、打電話等),如圖
傳統方式解決手機操作問題
類圖
分析
- 擴充套件性問題(類爆炸),如果我們再增加手機的樣式(旋轉式),就需要增加各個品牌手機的類,同樣如果我們增加一個手機品牌,也要在各個手機樣式類下增加。
- 違反了單一職責原則,當我們增加手機樣式時,要同時增加所有品牌的手機,這樣增加了程式碼維護成本
- 解決方案:橋接模式
2,橋接模式
a)基本介紹
橋接模式(Bridge模式)是指:將實現與抽象放在兩個不同的類層次中,使兩個層次可以獨立改變。它是一種結構型設計模式。
Bridge模式基於類的最小設計原則,通過使用封裝、聚合及繼承等行為讓不同的類承擔不同的職責。它的主要特點是把抽象(Abstraction)與行為實現(Implementation)分離開來,從而可以保持各部分的獨立性以及應對他們的功能擴充套件。
b)原理類圖
原理類圖說明:
- Client類:橋接模式的呼叫者
- 抽象類(Abstraction):維護了Implementor/即它的實現類ConcreteImplementorA/B,二者是聚合關係,Abstraction充當橋接
- RefinedAbstraction:是Abstraction抽象類的子類
- Implementor:行為實現類的介面
- ConcreteImplementorA/B:行為的具體實現類
- 從UML圖:這裡的抽象類和介面是聚合的關係,其實也是呼叫和被呼叫關係
c)解決手機操作問題
類圖
public interface Brand { void open(); void call(); void close(); } public class Xiaomi implements Brand{ @Override public void open() { System.out.println("小米手機開機"); } @Override public void call() { System.out.println("小米手機打電話"); } @Override public void close() { System.out.println("小米手機關機"); } } public abstract class Phone { private Brand brand; public Phone(Brand brand) { this.brand = brand; } protected void open() { this.brand.open(); } protected void call() { this.brand.call(); } protected void close() { this.brand.close(); } } public class FoldedPhone extends Phone{ public FoldedPhone(Brand brand) { super(brand); } @Override public void open() { super.open(); System.out.println("摺疊樣式手機"); } @Override protected void call() { super.call(); System.out.println("摺疊樣式手機"); } @Override protected void close() { super.close(); System.out.println("摺疊樣式手機"); } } public class Client { public static void main(String[] args) { FoldedPhone foldedPhone = new FoldedPhone(new Xiaomi()); foldedPhone.open(); } }
3,注意事項
- 實現了抽象和實現部分的分離,從而極大的提高了系統的靈活性,讓抽象部分和實現部分獨立開來,這有助於系統進行分層設計,從而產生更好的結構化系統
- 對於系統的高層部分,只需要知道抽象部分和實現部分的介面就可以了,其它部分由具體業務來完成
- 橋接模式替代多層繼承方案,可以減少子類的個數,降低系統的管理和維護成本
- 橋接模式的引入增加了系統的理解和設計難度,由於聚合關聯關係建立在抽象層,要求開發者對抽象進行設計和程式設計
- 橋接模式要求正確識別出系統中兩個獨立變化的維度(抽象和實現),因此其使用範圍有一定的侷限性
4,常用的場景
- JDBC驅動
-
銀行轉賬系統
- 轉賬分類: 網上轉賬,櫃檯轉賬,AMT 轉賬
- 轉賬使用者型別:普通使用者,銀卡使用者,金卡使用者...
- 訊息管理
- 訊息型別:即時訊息,延時訊息
- 訊息分類:手機簡訊,郵件訊息,QQ 訊息...
四、裝飾者模式
1,咖啡訂單問題
- 咖啡種類/單品咖啡:Espresso(義大利濃咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(無因咖啡)
- 調料:Milk、Soy(豆漿)、Chocolate
- 要求在擴充套件性的咖啡種類時,具有良好的擴充套件性、改動方便、維護方便
- 使用OO的方式來計算不同種類咖啡的費用:單品咖啡、單品+調料組合等
2,解決方案
a)方案一
類圖分析:
1)Drink 是一個抽象類,表示飲料
-description 就是對咖啡的描述, 比如咖啡的名字
-cost()方法就是計算費用,Drink 類中做成一個抽象方法.
2)Decaf 就是單品咖啡,繼承 Drink, 並實現了 cost()
3)Espress && Milk 就是單品咖啡+調料, 這個組合很多
問題:
這樣設計會有很多類,當我們增加一個單品咖啡,或者一個新的調料時,類的數量就會倍增,就會出現類爆炸。
b)方案二
說明: milk,soy,chocolate 可以設計為 Boolean,表示是否要新增相應的調料.
方案二問題分析:
1) 方案 2 可以控制類的數量,不至於造成很多的類
2) 在增加或者刪除調料種類時,程式碼的維護量很大
3) 考慮到使用者可以新增多份調料,可以將 hasMilk 返回一個對應 int
4) 改進:考慮使用 裝飾者 模式
3,裝飾者模式
a)基本介紹
裝飾者模式(Decorator):動態地將新功能附加到物件上。在物件功能擴充套件方面,它比繼承更有彈性,裝飾者模式也體現了開閉原則(OCP)。
b)原理
- 裝飾者模式就像打包一個快遞
- 主體:陶瓷、衣服(Component) //被裝飾者
- 包裝:報紙填充、紙板、木板(Decorator)//裝飾者
- Component:主體,類似前面的Drink
- ConcreteComponent:具體的主體,比如前面的各種咖啡
- Decorator:裝飾者,比如各種調料。裝飾者裡聚合了一個Component,也就是ConcreteComponent是可以放到裝飾者裡面的(裝飾者裡面可以包含被裝飾者)
- ConcreteDecorator:具體的裝飾者,比如前面的各種調料
c)完成咖啡訂單問題
uml類圖
//飲料父類 public abstract class Drink { private String description; private float price = 0.0f; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } //消費由子類具體實現 public abstract float cost(); } //咖啡 public class Coffee extends Drink{ @Override public float cost() { return super.getPrice(); } @Override public String getDescription() { return super.getDescription() + ":" + cost(); } } //美式咖啡 public class LongBlack extends Coffee{ public LongBlack() { setDescription("美式...黑咖啡"); setPrice(2.0f); } } //裝飾類,調料 public class Decorator extends Drink{ Drink drink; public Decorator(Drink drink) { this.drink = drink; } @Override public float cost() { return drink.cost() + super.getPrice(); } @Override public String getDescription() { return super.getDescription() + " " + super.getPrice() + " " + drink.getDescription(); } } //具體的裝飾調料,牛奶 public class Milk extends Decorator{ public Milk(Drink drink) { super(drink); setPrice(1.5f); setDescription("牛奶"); } } //測試 public class Client { public static void main(String[] args) { Drink order = new LongBlack(); System.out.println("單品:"+order.getDescription()+" 價格:"+order.cost()); //加一份巧克力 order = new Chocolate(order); System.out.println("加一份巧克力:"+order.getDescription()+" 價格:"+order.cost()); order = new Milk(order); System.out.println("加一份巧克力和一份牛奶:"+order.getDescription()+" 價格:"+order.cost()); } }
4,JDK中的應用例項
Java的IO結構,FilterInputStream就是一個裝飾者
public abstract class InputStream implements Closeable { ...... //Component }
//Decorator抽象的裝飾者 class FilterInputStream extends InputStream { //被裝飾的物件 protected volatile InputStream in; ...... }
//具體的裝飾者 public class DataInputStream extends FilterInputStream implements DataInput { ...... }
- InputStream時抽象類,類似前面的Drink(被裝飾者)
- FileInputStream是InputStream子類,類似前面的LongBlack、ShortBlack等單品咖啡
- FIlterInputStream是InputStream子類,類似前面的Decorator裝飾者
- DataInputStream是FilterInputStream子類,具體的裝飾者,類似前面的Milk、Chocolate等