設計模式之結構型模式

mb61c3f0ed6fce2 2022-01-07 15:09:36 阅读数:251

模式 型模 模式



  • adapter 、bridge、facade

–adapter用於兩個不兼容接口之間的轉接

–bridge用於將一個抽象與多個可能的實現連接起來

–facade用於為複雜的子系統定義一個新的簡單易用的接口

  • composite、decorator和proxy

–composite用於構造對象組合結構

–decorator用於為對象增加新的職責

–proxy為目標對象提供一個替代者

  • flyweight

–針對細粒度對象的一種全局控制手段



裝飾模式

意圖:動態地給對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更靈活。裝飾模式是為已有的功能動態地添加更多功能的一種方式。在起初的設計當中,當系統需要新功能的時候,是向舊的類中添加新的代碼,這些新加的代碼,通常裝飾了原有類的核心職責或主要行為,在主類中加入了新的字段,新的方法和新的邏輯,從而增加了主類的複雜度,而這些新加入的東西僅僅是為了滿足一些只在某種特定情况下才會執行的特殊行為的需要。而裝飾模式卻提供了一個非常好的解决方案,它把每個要裝飾的功能都放在單純的類中,並讓這個類包裝它所要裝飾的對象(就是抽象類Decorator中的Component字段),因此,當需要執行特殊行為時,客戶代碼就可以在運行時根據需要有選擇地、按順序地使用裝飾功能包裝對象了。

例如,商店為了促銷,在不同時期將隨售出的衣服附送一些小禮物,例如在聖誕節期間,附送聖誕樹小掛件,在新年期間,附送賀卡紅包等。

–在這種情况下,這些小禮物本身並不屬於售出衣服的一部分,它們是在不同時期動態添加到售出衣服上的額外屬性

–而且由於一年中會展開很多次這樣的促銷,因此,子類化售出衣服,使其適用於各個時期,就會造成類的數量膨脹,系統難易維護

–此時,裝飾器模式就成了首選


角色及職責:


  • 抽象構件(Component)角色:給出一個抽象接口,以規範准備接收附加責任的對象。
  • 具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。
  • 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
  • 具體裝飾(Concrete Decorator)角色:負責給構件對象"貼上"附加的責任

特點:


  • 裝飾對象和真實對象有相同的接口。這樣客戶端對象就可以以和真實對象相同的方式和裝飾對象交互
  • 裝飾對象包含一個真實對象的索引(reference)
  • 裝飾對象接受所有的來自客戶端的請求。它把這些請求轉發給真實的對象
  • 裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展

適用範圍:


  • 需要擴展一個類的功能,或給一個類增加附加責任。
  • 需要動態地給一個對象增加功能,這些功能可以再動態地撤銷。
  • 需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關系變得不現實。

優點:


  • 裝飾模式與繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。
  • 這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易於出錯。

缺點:


  • 由於使用裝飾模式,可以比使用繼承關系需要較少數目的類。使用較少的類,當然使設計比較易於進行。但是,在另一方面,使用裝飾模式會產生比使用繼承關系更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。
  • 一個裝飾類和它裝飾的組件並不是相同的。

實現:


  • 一個裝飾類的接口必須與被裝飾類的接口相容。
  • 盡量保持Component作為一個"輕"類,不要把太多的邏輯和狀態放在Component類裏。
  • 如果只有一個ConcreteComponent類而沒有抽象的Component類(接口),那麼Decorator類經常可以是ConcreteComponent的一個子類。
  • 如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合並成一個類。

透明性的要求
透明的裝飾模式

裝飾模式通常要求針對抽象編程。裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteDecorator類型的變量,而應當聲明一個Component類型的變量。換言之,下面的做法是對的:

Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);

而下面的做法是不對的:

ConcreteComponent c = new ConcreteDecorator();

這就是前面所說的,裝飾模式對客戶端是完全透明的含義。



半透明的裝飾模式

然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增强所考慮的類的性能。在增强性能的時候,往往需要建立新的公開的方法。即便是在孫大聖的系統裏,也需要新的方法。比如齊天大聖類並沒有飛行的能力,而雀兒有。這就意味著雀兒應當有一個新的fly()方法。這就導致了大多數的裝飾模式的實現都是"半透明"(semi-transparent)的,而不是完全"透明"的。換言之,允許裝飾模式改變接口,增加新的方法。即聲明ConcreteDecorator類型的變量,從而可以調用ConcreteDecorator類中才有的方法。



 還是泡茶的問題,現在我們想要給泡好的茶加入新的作料使它成為一種新品種。

首先定義裝飾類和被裝飾類的共同接口類Tea:


public abstract class Tea
{
boolean teaIsSteeped;
public abstract void steepTea();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

然後定義被裝飾者TeaLeaves,它僅僅使用幾片茶葉泡茶:

public class TeaLeaves extends Tea
{
public TeaLeaves() {
teaIsSteeped = false;
}
public void steepTea() {
teaIsSteeped = true;
System.out.println("tea leaves are steeping");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

裝飾者ChaiDecrator,它持有一個被裝飾者Tea的變量,並在構造函數中初始化它。它同樣實現了接口中定義的方法steepTea(),在被裝飾者中的steepTea()運行後,它還為持有的被裝飾者添加新的作料:


public class ChaiDecorator extends Tea {
private Tea teaToMakeChai;
private ArrayList chaiIngredients = new ArrayList();
public ChaiDecorator(Tea teaToMakeChai) {
this.addTea(teaToMakeChai);
chaiIngredients.add("bay leaf");
chaiIngredients.add("cinnamon stick");
chaiIngredients.add("ginger");
chaiIngredients.add("honey");
chaiIngredients.add("soy milk");
chaiIngredients.add("vanilla bean");
}
private void addTea(Tea teaToMakeChaiIn) {
this.teaToMakeChai = teaToMakeChaiIn;
}
public void steepTea() {
this.steepChai();
}
public void steepChai() {
teaToMakeChai.steepTea();
this.steepChaiIngredients();
System.out.println("tea is steeping with chai");
}
public void steepChaiIngredients() {
ListIterator listIterator = chaiIng redients.listIterator();
while (listIterator.hasNext()) {
System.out.println(((String)(listIterator.next())) + " is steeping");
}
System.out.println("chai ingredients are steeping");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

測試代碼:


class TestChaiDecorator {
public static void main(String[] args) {
Tea teaLeaves = new TeaLeaves();
Tea chaiDecorator = new ChaiDecorator(teaLeaves);
chaiDecorator.steepTea();
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.




 關於適配器模式和外觀模式,《Head First Design Pattern》一書在第七章的封面很有意思:


適配器模式包裝了某些對象,使它們的接口看起來不像自己而是像別的東西。這樣做的目的是為了在設計中,將類的接口轉換成想要的接口,以便實現不同的接口。而外觀模式則是將對象包裝起來以簡化其接口。

適配器模式

定義:編寫一個具有所需要接口的類,由他和擁有不同接口的類進行通信

意圖將一個類的程序設計接口轉換成另一個接口。複用已存在的接口與所需接口不一致的類。

現實生活中也有很多適配器,比如你買了一個臺灣版的水果牌手機,在大陸就不能直接充電(插頭的不一樣),你得買個交流電適配器轉換一下才能使用。OO中的適配器幹得也是同樣的事情。假設已有一個軟件系統,你希望它能和一個新的廠商類庫搭配使用,但這個新的廠商所設計出來的接口不同於舊廠商提供的接口。你不像改變現有的代碼,這時就可以使用適配器模式。你可以寫一個類,將新廠商接口轉換成你希望的接口。


實現



類適配器:從一個不一致的類派生出一個類,然後在派生類裏面增加所需要的方法,使得派生類能够匹配所需要的接口。它使用多重繼承來使得一個接口與另一個接口適配。




對象適配器:將原始類包含在新類裏,然後在新類裏創建方法去轉換調用。它依賴一個對象的組合




兩者比較:

設計模式之結構型模式_客戶端

角色及職責:


  • 源(Adaptee):已經存在的、需要適配的類。
  • 目標(Target):客戶所期待的接口。目標可以是具體的或抽象的類,也可以是接口。
  • 適配器(Adapter):適配器模式的核心類。有兩種方式實現Adapter,對象適配器(Object Adapter)和類適配器(Class Adapter)。
  • 客戶(Client):

UML圖


補充:更高層次的適配器

設計模式之結構型模式_裝飾模式_02

從左圖可以看出:Adapter將Adaptee接口適配為客戶Client需要的接口Target,這樣在整個系統中所有實現Adaptee接口的類都可以通過Adapter適配為Target對象,從而避免為每一個類都寫一個適配器。後面會給大家帶來一個JDK中使用此中適配的例子。我們不僅僅可以象上面一樣對接口進行適配,也可以對抽象類進行適配!主要是根據系統的需求,確定此時的場景是否適合使用適配器模式!

適用範圍:


  • 系統需要使用現有的類,而此類的接口不符合系統的需要
  • 想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。這些源類不一定有很複雜的接口。
  • (對對象適配器而言)在設計裏,需要改變多個已有子類的接口,如果使用類的適配器模式,就要針對每一個子類做一個適配器,而這不太實際。

 效果:

我們需要在類適配器和對象適配器之間做權衡

類適配器:


  • 因為類適配器是通過委托一個具體適配器類來使得被適配者適配於目標者的,這也意味著一個類適配器不可以適配一個類和它的所有子類。
  • 它不需要重新實現它的整個被適配者。必要的時候,還允許適配器覆蓋被適配者的一些行為,因為適配器實際上就是被適配者的一個子類。
  • 僅僅需要一個對象——類適配器,而不需要一個適配器和一個被適配者,不需要其他間接的指針來得到被適配者。
  • 需要使用多重繼承,這在像Java這樣的語言中是不可能實現的。

對象適配器:


  • 允許一個適配器同時與多個被適配者,即被適配者本身和它所有的子類,工作。適配器還可以為所有被適配者增加新的功能。
  • 但是很難覆蓋被適配者的行為。它需要把被適配者歸入子類,而讓適配器適用於子類而不是被適配者本身。

實現:

我們現在來看一個對象適配器的例子。考慮我們現在有一種茶葉,是由一種松散的茶葉構成。

被適配者(the adaptee):


public class LooseLeafTea {
boolean teaIsSteeped;
public LooseLeafTea() {
teaIsSteeped = false;
}
public void steepTea() {
teaIsSteeped = true;
System.out.println("tea is steeping");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

而隨著時代變遷,出現了茶包,即可以由很多不同的茶葉混合在一起做成一個包。我們有了一個新的目標(the target):


public class TeaBag {
boolean teaBagIsSteeped;
public TeaBag() {
teaBagIsSteeped = false;
}
public void steepTeaInCup() {
teaBagIsSteeped = true;
System.out.println("tea bag is steeping in cup");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

我們再回顧下對象適配器的做法:它首先繼承目標類(這裏是TeaBag),然後將原始類(這裏是LooseLeafTea)包含在新類(適配器TeaBall)裏,然後在新類裏創建方法去轉換調用。適配器(the adapter):



public class TeaBall extends TeaBag {
LooseLeafTea looseLeafTea;
public TeaBall(LooseLeafTea looseLeafTeaIn) {
looseLeafTea = looseLeafTeaIn;
teaBagIsSteeped = looseLeafTea.teaIsSteeped;
}
public void steepTeaInCup() {
looseLeafTea.steepTea();
teaBagIsSteeped = true;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

客戶類(the client),它請求一個TeaBag來進行泡茶,通過適配器,我們使得它同樣可以用以前的茶葉(LooseLeafTea)泡茶:


public class TeaCup {
public void steepTeaBag(TeaBag teaBag) {
teaBag.steepTeaInCup();
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

測試代碼:


class TestTeaBagAdaptation {
public static void main(String[] args) {
TeaCup teaCup = new TeaCup();
System.out.println("Steeping tea bag");
TeaBag teaBag = new TeaBag();
teaCup.steepTeaBag(teaBag);
System.out.println("Steeping loose leaf tea");
LooseLeafTea looseLeafTea = new LooseLeafTea();
TeaBall teaBall = new TeaBall(looseLeafTea);
teaCup.steepTeaBag(teaBall);
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

通過上面的例子,我們可以看到,通過給適配器(TeaBall)傳遞一個被適配者(LooseLeafTea)的對象,我們得到了一個和目標(TeaBag)具有相同接口的對象,我們想泡哪種茶都可以啦!


總結:

我們再來看看各部分的關系。


客戶使用適配器的過程如下:1)客戶通過目標接口調用適配器的方法對適配器發出請求;2)適配器使用被適配者接口把請求轉換成被適配者的一個或多個調用接口;3)客戶接收到調用的結果,但並未察覺這一切是適配器在起轉換作用,因此客戶和被適配者是解耦的,一個不知道另一個。




外觀模式(Facade)

意圖:為子系統提供了一個更高層次、更簡單的接口,從而降低了子系統的複雜度和依賴。這使得子系統更易於使用和管理。對外提供一個統一的接口用來訪問子系統中的一群接口。外觀是一個能為子系統和客戶提供簡單接口的類。當正確的應用外觀,客戶不再直接和子系統中的類交互,而是與外觀交互。外觀承擔與子系統中類交互的責任。實際上,外觀是子系統與客戶的接口,這樣外觀模式降低了子系統和客戶的耦合度。外觀對象隔離了客戶和子系統對象,從而降低了耦合度。當子系統中的類進行改變時,客戶端不會像以前一樣受到影響。

設計模式之結構型模式_子類_03

角色及職責:


  • 門面(Facade)這個外觀類為子系統中Packages 1、2、3提供一個共同的對外接口
  • 客戶(Clients)客戶對象通過一個外觀接口讀寫子系統中各接口的數據資源。
  • 子系統(Packages)客戶可以通過外觀接口讀取的內部庫。子系統並不知道門面的存在,對於子系統而言,門面僅僅是另外一個客戶端而已。 

適用範圍:


  • 為複雜的子系統提供一個簡單的接口。不需要使用一個複雜系統的所有功能,而且可以創建一個新的類,包含訪問系統的所有規則。如果只需要使用系統的部分功能,那麼你為新類所創建的API將比原系統的API簡單的多。
  • 在客戶代碼和一個抽象的實現之間存在很多依賴關系,通過一個facade來讓客戶代碼和子系統之間實現解耦。
  • 希望將子系統分層,用多個facade為每個子系統等級定義一個入口。
  • 希望使用原系統的功能,而且還希望增加一些新的功能。
  • 編寫新類的成本小於所有人學會使用或者未來維護原系統上所需的成本。

 效果:


  • 它將客戶從子系統組件中分離出來,降低了客戶代碼交互的對象的數量,使得子系統更加易於使用。
  • 它使得子系統和它的客戶實現松耦合。
  • 它並不對應用程序訪問子系統造成阻礙,它依然將系統完整的功能暴露出來,以供需要的人使用。

來看一個例子。假想你給家裏買了一套家庭影院系統(發獎金啦),這套系統很高級,內置DVD播放器、投影儀、自動屏幕、環繞立體聲,甚至還有爆米花機……

這些組件如下:


你好不容易排好線,連接好各種設備,終於可以看電影啦!可是…… 看影片哪有那麼容易,說明書上告訴你必須這麼做:

而且看完之後,你還得倒著做一邊來關掉電影。這時你就非常需要一個外觀模式啦!


那麼如何構造家庭影院外觀呢?第一步,使用組合讓外觀能够訪問子系統中所有的組件(這裏指投影儀、DVD機、燈光等等):


然後實現看電影和關電影的方法:


剩下的就很簡單啦!


附加:裝飾者模式、適配器模式和外觀模式的區別。


  • 適配器將一個對象包裝起來以改變其接口;裝飾者將一個對象包裝起來以增加新的行為和責任;而外觀模式將一群對象“包裝”起來以簡化其接口。



橋接模式:

意圖:將抽象部分與實現部分分離,使得它們兩個部分可以獨立的變化。

設計模式之結構型模式_客戶端_04

等級結構:​​


  • 由抽象化角色和修正抽象化角色組成的抽象化等級結構。
  • 由實現化角色和兩個具體實現化角色所組成的實現化等級結構。

角色及職責:


  • 抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現化對象的引用
  • 修正抽象化(Refined Abstraction)角色:擴展抽象化角色,改變和修正父類對抽象化的定義。
  • 實現化(Implementor)角色:這個角色給出實現化角色的接口,但不給出具體的實現。必須指出的是,這個接口不一定和抽象化角色的接口定義相同,實際上,這兩個接口可以非常不一樣。實現化角色應當只給出底層操作,而抽象化角色應當只給出基於底層操作的更高一層的操作。
  • 具體實現化(Concrete Implementor)角色:這個角色給出實現化角色接口的具體實現。

優勢和缺陷:

橋接模式可以從接口分離實現功能,使得設計更具有擴展性,這樣,客戶調用方法是根本不需要知道實現的細節。橋接模式的優點是减少了子類,如果程序中要在2個操作系統中實現查看6種圖像格式,那麼就會有2*6個類。使用橋接模式時候就會變成2+6個類(2個類定義了兩個操作系統的抽象,6個類定義了查看了六種圖像格式,因為沒有把抽象和實現綁定在一起,因此只需要組合一下2種抽象和6種實現就可以達到目的)了,它使代碼變得更清潔了,生成的執行程序更小了。但是橋接模式的缺陷是抽象類與實現類的雙向連接使得運行速度更慢了

適用場景


  • 避免抽象方法和其實現方法永久地綁定在一起。
  • 抽象接口和它的實現都需要擴展出子類以備使用。
  • 變動實現的方法根本不會影響客戶程序調用的部分(甚至不用重新編譯)。
  • (C++)你想要對客戶隱藏一個抽象類的實現。
  • 錶明需要把一個對象分解成兩個部分。
  • 你想要與多個對象共享一種實現方法(可能使用引用計數),並且這個事實應當對客戶隱藏。

效果:



  • 將接口和實現進行解耦。
  • 增加了可擴展性。
  • 對客戶隱藏了實現細節。

實現:




  • 只有一個實現者(Implementor)
  • 創建正確的實現者對象。
  • 共享實現。
  • 使用多重繼承。

還是來看一個例子。考慮我們現在要制作兩種沙拉(2中抽象化角色):五分熟沙拉(MediumSoda)和超級沙拉(SuperSizeSoda),每種沙拉我們又希望有3種不同的口味(3種實現方法):草莓味(CherrySodaImp)、葡萄味(GrapeSodaImp)和橘子味(OrangeSodaImp)。如果不使用橋接模式,我們需要定義六個類:草莓味五分熟沙拉、葡萄味五分熟沙拉、橘子味五分熟沙拉和草莓味超級沙拉、葡萄味超級沙拉、橘子味超級沙拉,而且以後如果我們有了更多的口味(比如西瓜味、水蜜桃味等等),我們需要定義更多的類!而使用橋接模式後,我們就可以把抽象和實現方法隔離開來。



首先看抽象化(Abstraction)角色,它保存一個對實現化對象(這裏指SodaImp)的引用


public abstract class Soda {
SodaImp sodaImp;
public void setSodaImp() {
this.sodaImp = SodaImpSingleton.getTheSodaImp();
}
public SodaImp getSodaImp() {
return this.sodaImp;
}
public abstract void pourSoda();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

兩個修正抽象化(Refined Abstraction) 角色:

五分熟沙拉(MediumSoda):


public class MediumSoda extends Soda {
public MediumSoda() {
setSodaImp();
}
public void pourSoda() {
SodaImp sodaImp = this.getSodaImp();
for (int i = 0; i < 2; i++) {
System.out.print("...glug...");
sodaImp.pourSodaImp();
}
System.out.println(" ");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.


超級沙拉(SuperSizeSoda):


public class SuperSizeSoda extends Soda {
public SuperSizeSoda() {
setSodaImp();
}
public void pourSoda() {
SodaImp sodaImp = this.getSodaImp();
for (int i = 0; i < 5; i++) {
System.out.print("...glug...");
sodaImp.pourSodaImp();
}
System.out.println(" ");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

可以看到兩種沙拉都持有一個沙拉的實現化角色,並在構造函數中初始化它。在我們的例子裏,通過一個單件來控制當前是哪種實現方法:


public class SodaImpSingleton {
private static SodaImp sodaImp;
public SodaImpSingleton(SodaImp sodaImpIn) {
this.sodaImp = sodaImpIn;
}
public static SodaImp getTheSodaImp() {
return sodaImp;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.


下面是實現化(Implementor)角色,它給出實現化角色的接口,但不給出具體的實現:

public abstract class SodaImp {
public abstract void pourSodaImp();
}
  • 1.
  • 2.
  • 3.

三種具體實現化(Concrete Implementor)角色:

草莓味(CherrySodaImp):


public class CherrySodaImp extends SodaImp {
CherrySodaImp() {}
public void pourSodaImp() {
System.out.println("Yummy Cherry Soda!");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.


葡萄味(GrapeSodaImp):


public class GrapeSodaImp extends SodaImp {
GrapeSodaImp() {}
public void pourSodaImp() {
System.out.println("Delicious Grape Soda!");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

橘子味(OrangeSodaImp):


public class OrangeSodaImp extends SodaImp {
OrangeSodaImp() {}
public void pourSodaImp() {
System.out.println("Citrusy Orange Soda!");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

最後是測試代碼,我們用三種口味來制作沙拉:


class TestBridge {
public static void testCherryPlatform() {
SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new CherrySodaImp());
        MediumSoda mediumSoda = new MediumSoda();
mediumSoda.pourSoda();
SuperSizeSoda superSizeSoda = new SuperSizeSoda();
superSizeSoda.pourSoda();
}
public static void testGrapePlatform() {
SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new GrapeSodaImp());
MediumSoda mediumSoda = new MediumSoda();
mediumSoda.pourSoda();
SuperSizeSoda superSizeSoda = new SuperSizeSoda();
superSizeSoda.pourSoda();
}
public static void testOrangePlatform() {
SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new OrangeSodaImp());
MediumSoda mediumSoda = new MediumSoda();
mediumSoda.pourSoda();
SuperSizeSoda superSizeSoda = new SuperSizeSoda();
superSizeSoda.pourSoda();
}
public static void main(String[] args) {
testCherryPlatform();
testGrapePlatform();
testOrangePlatform();
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.



組合模式

意圖:將對象組合成樹形結構以錶示"部分-整體"的層次結構。Composite模式使得用戶對單個對象和組合對象的使用具有一致性。組合模式有時候又叫做部分-整體模式,它使我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程序可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程序與複雜元素的內部結構解耦。

例如論壇裏的回帖及每個回帖的回複,設計人員希望能够以統一的方式處理某個具體的帖子和某個帖子以及跟帖構成的樹或子樹,就可以使用組合模式。

設計模式之結構型模式_客戶端_05

透明模式

設計模式之結構型模式_子類_06

安全模式

分類:


  • 安全式的組合模式:管理聚集的方法(add()、remove()、getChild()等)只出現在組合對象中,而不出現在簡單對象中。
  • 透明式的組合模式:所有的對象,不論組合對象還是簡單對象,均符合一個固定的接口。在簡單對象中管理聚集的方法的實現為空。

效果及實現要點:


  • Composite模式采用樹形結構來實現普遍存在的對象容器,從而將"一對多"的關系轉化"一對一"的關系,使得客戶代碼可以一致地處理對象和對象容器,無需關心處理的是單個的對象,還是組合的對象容器。
  • 將"客戶代碼與複雜的對象容器結構"解耦是Composite模式的核心思想,解耦之後,客戶代碼將與純粹的抽象接口——而非對象容器的複內部實現結構——發生依賴關系,從而更能"應對變化"。
  • Composite模式中,是將"Add和Remove等和對象容器相關的方法"定義在"錶示抽象對象的Component類"中,還是將其定義在"錶示對象容器的Composite類"中,是一個關乎"透明性"和"安全性"的兩難問題,需要仔細權衡。這裏有可能違背面向對象的"單一職責原則",但是對於這種特殊結構,這又是必須付出的代價。ASP.NET控件的實現在這方面為我們提供了一個很好的示範。
  • Composite模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍曆需求,可使用緩存技巧來改善效率。

適用性:


  • 你想錶示對象的部分-整體層次結構
  • 你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。

 我們還是考慮茶包。現在假設我們有一個大包可以裝下很多小的單個茶葉包,同時它也可以裝其他包含單個茶葉包的茶葉包,因為大家都是茶葉包嘛~


首先設計一個公共的接口類TeaBags,它定義了不管是簡單對象(OneTeaBag)還是對象容器(TinOfTeaBags)功能的接口:


public abstract class TeaBags {
LinkedList teaBagList;
TeaBags parent;
String name;
public abstract int countTeaBags();
public abstract boolean add(TeaBags teaBagsToAdd);
public abstract boolean remove(TeaBags teaBagsToRemove);
public abstract ListIterator createListIterator();
public void setParent(TeaBags parentIn) {
parent = parentIn;
}
public TeaBags getParent() {
return parent;
}
public void setName(String nameIn) {
name = nameIn;
}
public String getName() {
return name;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

接下來是簡單對象(OneTeaBag),注意都實際上所有的聚集操作(public boolean add(TeaBags teaBagsToAdd),public boolean remove(TeaBags teaBagsToRemove),public ListIterator createListIterator())它都不用實現:


public class OneTeaBag extends TeaBags {
public OneTeaBag(String nameIn) {
this.setName(nameIn);
}
public int countTeaBags() {
return 1;
}
public boolean add(TeaBags teaBagsToAdd) {
return false;
}
public boolean remove(TeaBags teaBagsToRemove) {
return false;
}
public ListIterator createListIterator() {
return null;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

對象容器(TinOfTeaBag):


public class TinOfTeaBags extends TeaBags {
public TinOfTeaBags(String nameIn) {
teaBagList = new LinkedList();
this.setName(nameIn);
}
public int countTeaBags() {
int totalTeaBags = 0;
ListIterator listIterator = this.createListIterator();
TeaBags tempTeaBags;
while (listIterator.hasNext()) {
tempTeaBags = (TeaBags)listIterator.next();
totalTeaBags += tempTeaBags.countTeaBags();
}
return totalTeaBags;
}
public boolean add(TeaBags teaBagsToAdd) {
teaBagsToAdd.setParent(this);
return teaBagList.add(teaBagsToAdd);
}
public boolean remove(TeaBags teaBagsToRemove) {
ListIterator listIterator = this.createListIterator();
TeaBags tempTeaBags;
while (listIterator.hasNext()){
tempTeaBags = (TeaBags)listIterator.next();
if (tempTeaBags == teaBagsToRemove) {
listIterator.remove();
return true;
}
}
return false;
}
public ListIterator createListIterator() {
ListIterator listIterator = teaBagList.listIterator();
return listIterator;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

最後是測試代碼:


class TestTeaBagsComposite
{
public static void main(String[] args){
TeaBags tinOfTeaBags = new TinOfTeaBags("tin of tea bags"); // 先創建一個對象容器tinOfTeaBags
TeaBags teaBag1 = new OneTeaBag("tea bag 1"); //再創建兩個簡單對象
TeaBags teaBag2 = new OneTeaBag("tea bag 2");

tinOfTeaBags.add(teaBag1); // 向對象容器中添加這兩個簡單對象
tinOfTeaBags.add(teaBag2);

System.out.println("The tinOfTeaBags now has " + tinOfTeaBags.countTeaBags() + " tea bags in it.");
System.out.println(" ");

TeaBags smallTinOfTeaBags = new TinOfTeaBags("small tin of tea bags"); // 再定義另一個對象容器smallTinOfTeaBags
TeaBags teaBag3 = new OneTeaBag("tea bag 3"); // 同樣向這個新的對象容器中添加一個新的簡單對象
smallTinOfTeaBags.add(teaBag3);
System.out.println("The smallTinOfTeaBags now has " + smallTinOfTeaBags.countTeaBags() + " tea bags in it.");

tinOfTeaBags.add(smallTinOfTeaBags); // 向對象容器tinOfTeaBags添加對象容器smallTinOfTeaBags,可以看出它和添加一個簡單對象是完全相同的
System.out.println("The tinOfTeaBags now has " + t inOfTeaBags.countTeaBags() + " tea bags in it.");
System.out.println(" ");

tinOfTeaBags.remove(teaBag2); // 從對象容器tinOfTeaBag中移除另一個對象容器
System.out.println("The tinOfTeaBags now has " + tinOfTeaBags.countTeaBags() + " tea bags in it.");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.



享元模式

意圖:用一個共享來避免大量擁有相同內容對象的開銷。這種開銷中最常見、直觀的就是內存的損耗。享元模式以共享的方式高效的支持大量的細粒度對象。

對象的狀態:


  • 內蘊狀態(Internal State)內蘊狀態存儲在享元對象內部且不會隨環境改變而改變。因此內蘊狀態並可以共享。
  • 外蘊狀態(External State)。外蘊狀態是隨環境改變而改變的、不可以共享的狀態。享元對象的外蘊狀態必須由客戶端保存,並在享元對象被創建之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態與內蘊狀態是相互獨立的。

適用範圍:


  • 一個系統有大量的對象。
  • 這些對象耗費大量的內存。
  • 這些對象的狀態中的大部分都可以外部化。
  • 這些對象可以按照內蘊狀態分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。
  • 軟件系統不依賴於這些對象的身份,換言之,這些對象可以是不可分辨的。

分類:

  • 單純享元模式 

角色及職責:

1>:抽象享元(Flyweight)角色:此角色是所有的具體享元類的超類,為這些類規定出需要實現的公共接口。那些需要外蘊狀態(External State)的操作可以通過調用商業方法以參數形式傳入

/// <summary>
    
/// "Flyweight"
    
/// </summary>
    abstract class Flyweight
    {
        
// Methods
        /// <summary>
        
/// 抽象享元對象的商業方法
        
/// </summary>
        
/// <param name="extrinsicstate">外蘊狀態</param>
        abstract public void Operation(int extrinsicstate);
    }

2>:具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的接口。如果有內蘊狀態的話,必須負責為內蘊狀態提供存儲空間。享元對象的內蘊狀態必須與對象所處的周圍環境無關,從而使得享元對象可以在系統內共享的。

/// <summary>
    
/// "ConcreteFlyweight"
    
/// </summary>
    class ConcreteFlyweight : Flyweight
    {
        
private string intrinsicstate = "A";
        
// Methods
        override public void Operation(int extrinsicstate)
        {
            Console.WriteLine(
"ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}",intrinsicstate, extrinsicstate);
        }
    }

3>:享元工廠(FlyweightFactory)角色:本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。

注意:客戶端不可以直接實例化享元類,必須通過享元工廠類來創建,因為享元工廠類在系統中只能有一個,所以可以結合單件模式來改善。當客戶端需要單純享元對象時,需要調用享元工廠的Singleton()方法,此時工廠會取得所有的單純享元對象,然後傳入所需的單純享元對象的內蘊狀態,工廠方法負責產生所需要的享元對象。

/// <summary>
    
/// "FlyweightFactory"
    
/// </summary>
    class FlyweightFactory
    {
        
// Fields
        private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
        
private static readonly FlyweightFactory instance = new FlyweightFactory();
        
/// <summary>
        
/// Constructors
        
/// </summary>
        private  FlyweightFactory()
        {          
        }
        
// Methods
        /// <summary>
        
/// 從享元工廠中生產出一個具體的享元對象
        
/// </summary>
        
/// <param name="key">內蘊狀態</param>
        
/// <returns></returns>
        public Flyweight GetFlyweight(string key)
        {
            
return ((Flyweight)flyweights[key]);
        }
        
/// <summary>
        
/// 享元工廠單例方法
        
/// </summary>
        
/// <returns></returns>
        public static  FlyweightFactory Singleton()
        {
            
return FlyweightFactory.instance;
        } 
        
/// <summary>
        
/// 向享元工廠對象增加一個享元對象
        
/// </summary>
        
/// <param name="sKey">內蘊狀態</param>
        
/// <param name="_Flyweight">具體享元對象</param>
        public void AddFlyweight(string sKey, Flyweight _Flyweight)
        {
            flyweights.Add(sKey , _Flyweight); 
        }
        
public Flyweight factory(string sKey)
        {
            
if (flyweights.ContainsKey(sKey))
            {
                
return this.GetFlyweight(sKey);
            }
            
else
            {
                
this.AddFlyweight(sKey, new ConcreteFlyweight());
                
return this.GetFlyweight(sKey);
            }      
        }
    }

4>:客戶端(Client)角色:需要維護一個對所有享元對象的引用;需要自行存儲所有享元對象外蘊狀態。

// 初始化外蘊狀態值
            int extrinsicstate = 22;
            
//享元工廠對象使用單例
            FlyweightFactory f = FlyweightFactory.Singleton () ;
         
            
//調用過程
            //向享元工廠對象請求一個內蘊狀態為"X"的單純享元對象
            Flyweight fx = f.factory("X");
            
//調用X的商業方法,X的外蘊狀態值為21
            fx.Operation(--extrinsicstate);

            Flyweight fy = f.factory(
"Y");
            fy.Operation(--extrinsicstate);

            Flyweight fz = f.factory(
"Z");
            fz.Operation(--extrinsicstate);

複合享元模式。

        設計模式之結構型模式_裝飾模式_07 

1>:抽象享元角色:此角色是所有的具體享元類的超類,為這些類規定出需要實現的公共接口。那些需要外蘊狀態(External State)的操作可以通過方法的參數傳入。抽象享元的接口使得享元變得可能,但是並不强制子類實行共享,因此並非所有的享元對象都是可以共享的。
2>:具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的接口。如果有內蘊狀態的話,必須負責為內蘊狀態提供存儲空間。享元對象的內蘊狀態必須與對象所處的周圍環境無關,從而使得享元對象可以在系統內共享。有時候具體享元角色又叫做單純具體享元角色,因為複合享元角色是由單純具體享元角色通過複合而成的。
3>:複合享元(UnsharableFlyweight)角色:複合享元角色所代錶的對象是不可以共享的,但是一個複合享元對象可以分解成為多個本身是單純享元對象的組合。複合享元角色又稱作不可共享的享元對象。
4>:享元工廠(FlyweightFactoiy)角色:本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象請求一個享元對象的時候,享元工廠角色需要檢查系統中是否已經有一個符合要求的享元對象,如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個新的合適的享元對象。
5>:客戶端(Client)角色:本角色還需要自行存儲所有享元對象的外蘊狀態。

優點: 大幅度地降低內存中對象的數量。

缺點:


  • 享元模式使得系統更加複雜。為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯複雜化。
  • 享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

總結:

享元模式一般是解决系統性能問題的,所以經常用於底層開發,在項目開發中並不常用.


代理模式

意圖:為其他對象提供一種代理以控制對這個對象的訪問。




角色及職責


  • 抽象主題角色:聲明了真實主題和代理主題的共同接口。
  • 代理主題角色:內部包含對真實主題的引用,並且提供和真實主題角色相同的接口。
  • 真實主題角色:定義真實的對象。

適用範圍:


  • 遠程(Remote)代理:為一個比特於不同的地址空間的對象提供一個局域代錶對象。比如:你可以將一個在世界某個角落一臺機器通過代理假象成你局域網中的一部分。
  • 虛擬(Virtual)代理:根據需要將一個資源消耗很大或者比較複雜的對象延遲的真正需要時才創建。比如:如果一個很大的圖片,需要花費很長時間才能顯示出來,那麼當這個圖片包含在文檔中時,使用編輯器或瀏覽器打開這個文檔,這個大圖片可能就影響了文檔的閱讀,這時需要做個圖片Proxy來代替真正的圖片。
  • 保護(Protect or Access)代理:控制對一個對象的訪問權限。比如:在論壇中,不同的身份登陸,擁有的權限是不同的,使用代理模式可以控制權限(當然,使用別的方式也可以實現)。
  • 智能引用(Smart Reference)代理:提供比對目標對象額外的服務。比如:紀錄訪問的流量(這是個再簡單不過的例子),提供一些友情提示等等。

實現:


  • 指明一系列接口創建一個代理對象
  • 創建調用處理器對象
  • 將這個代理指定為其他代理的代理對象

在調用處理器的invoke()方法中采取處理,一方面將調用傳遞給真是對象,另一方面執行各種所需要的操作。

版权声明:本文为[mb61c3f0ed6fce2]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071509353194.html