天天看點

設計模式-結構型模式-超級棒的例子! GoF

目錄

GoF 23(分類)

OOP 七大原則

StructuralPatterns

Adapter

适用場景

執行個體-實作

與其他模式的關系

Bridge

背景

Composite

Decorator

Facade

使用場景

Flyweight

Proxy

閱讀推薦:設計模式-簡單篇

項目位址:https://gitee.com/zwtgit/gof23

學習網站推薦:

https://refactoringguru.cn/design-patterns/catalog

https://www.journaldev.com/1827/java-design-patterns-example-tutorial

設計模式是針對軟體設計中常見問題的工具箱, 其中的工具就是各種經過實踐驗證的解決方案。 即使你從未遇到過這些問題, 了解模式仍然非常有用, 因為它能指導你如何使用面向對象的設計原則來解決各種問題。

算法更像是菜單: 提供達成目标的明确步驟。 而模式更像是藍圖: 你可以看到最終的結果和模式的功能, 但需要自己确定實作步驟。

建立型模式:提供建立對象的機制, 增加已有代碼的靈活性和可複用性。

結構型模式:介紹如何将對象和類組裝成較大的結構, 并同時保持結構的靈活和高效。

行為模式:負責對象間的高效溝通和職責委派。

設計模式-結構型模式-超級棒的例子! GoF

建立型模式:

單例模式,工廠模式, 抽象工廠模式, 建造者模式, 原型模式

結構型模式:

擴充卡模式, 橋接模式, 裝飾模式, 組合模式, 外觀模式, 享元模式, 代理模式

行為型模式:

模闆方法模式, 指令模式, 疊代器模式, 觀察者模式, 中介者模式, 備忘錄模式, 解釋器模式, 狀态模式, 政策模式, 職責鍊模式, 通路者模式

面向對象程式設計(Object Oriented Programming,OOP)。

1、開閉原則

對擴充開放, 對修改關閉。

2、單一職責原則

每個類應該實作單一的職責,不要存在多于一個導緻類變更的原因,否則就應該把類拆分。該原則是實作高内聚、低耦合的指導方針。

3、裡氏替換原則(Liskov Substitution Principle)

任何基類可以出現的地方,子類一定可以出現。裡氏替換原則是繼承複用的基石,隻有當衍生類可以替換基類,軟體機關的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。

裡氏替換原則是對開閉原則的補充。實作開閉原則的關鍵就是抽象化。而基類與子類的繼承關系就是抽象化的具體實作,是以裡氏替換原則是對實作抽象化的具體步驟的規範。裡氏替換原則中,子類對父類的方法盡量不要重寫和重載。因為父類代表了定義好的結構,通過這個規範的接口與外界互動,子類不應該随便破壞它。

4、依賴倒轉原則(Dependence Inversion Principle)

面向接口程式設計,依賴于抽象而不依賴于具體。用到具體類時,不與具體類互動,而與具體類的上層接口互動。

5、接口隔離原則(Interface Segregation Principle)

每個接口中不存在子類用不到卻必須實作的方法,否則就要将接口拆分。使用多個隔離的接口,比使用單個接口(多個接口中的方法聚合到一個的接口)要好。

6、迪米特法則(最少知道原則)(Demeter Principle)

一個類對自己依賴的類知道的越少越好。無論被依賴的類多麼複雜,都應該将邏輯封裝在方法的内部,通過 public 方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。

7、合成複用原則(Composite Reuse Principle)

軟體複用時,要先盡量使用組合或者聚合等關聯關系實作,其次才考慮使用繼承。即在一個新對象裡通過關聯的方式使用已有對象的一些方法和功能。

擴充卡模式是一種結構型設計模式, 它能使接口不相容的對象能夠互相合作。

設計模式-結構型模式-超級棒的例子! GoF

對象擴充卡:實作時使用了構成原則: 擴充卡實作了其中一個對象的接口, 并對另一個對象進行封裝。

類擴充卡: 這一實作使用了繼承機制: 擴充卡同時繼承兩個對象的接口。

當你希望使用某個類, 但是其接口與其他代碼不相容時, 可以使用擴充卡類。

如果您需要複用這樣一些類, 他們處于同一個繼承體系, 并且他們又有了額外的一些共同的方法, 但是這些共同的方法不是所有在這一繼承體系中的子類所具有的共性。

使用示例: 擴充卡模式在 Java 代碼中很常見。 基于一些遺留代碼的系統常常會使用該模式。 在這種情況下, 擴充卡讓遺留代碼與現代的類得以互相合作。

Java 核心程式庫中有一些标準的擴充卡:

<code>java.util.Arrays#asList()</code>

<code>java.util.Collections#list()</code>

<code>java.util.Collections#enumeration()</code>

<code>java.io.InputStreamReader(InputStream)</code> (傳回 <code>Reader</code>對象)

<code>java.io.OutputStreamWriter(OutputStream)</code> (傳回 <code>Writer</code>對象)

<code>javax.xml.bind.annotation.adapters.XmlAdapter#marshal()</code> 和 <code>#unmarshal()</code>

識别方法: 擴充卡可以通過以不同抽象或接口類型執行個體為參數的構造函數來識别。 當擴充卡的任何方法被調用時, 它會将參數轉換為合适的格式, 然後将調用定向到其封裝對象中的一個或多個方法。

讓方釘适配圓孔

這個簡單的例子展示了擴充卡如何讓不相容的對象互相合作。

round

round/RoundHole.java: 圓孔

round/RoundPeg.java: 圓釘

square

square/SquarePeg.java: 方釘

adapters

adapters/SquarePegAdapter.java: 方釘到圓孔的擴充卡

Demo.java: 用戶端代碼

OutputDemo.txt: 執行結果

橋接模式通常會于開發前期進行設計, 使你能夠将程式的各個部分獨立開來以便開發。 另一方面, 擴充卡模式通常在已有程式中使用, 讓互相不相容的類能很好地合作。

擴充卡可以對已有對象的接口進行修改, 裝飾模式則能在不改變對象接口的前提下強化對象功能。 此外, 裝飾還支援遞歸組合, 擴充卡則無法實作。

擴充卡能為被封裝對象提供不同的接口, 代理模式能為對象提供相同的接口, 裝飾則能為對象提供加強的接口。

外觀模式為現有對象定義了一個新接口, 擴充卡則會試圖運用已有的接口。 擴充卡通常隻封裝一個對象, 外觀通常會作用于整個對象子系統上。

橋接、 狀态模式和政策模式 (在某種程度上包括擴充卡) 模式的接口非常相似。 實際上, 它們都基于組合模式——即将工作委派給其他對象, 不過也各自解決了不同的問題。 模式并不隻是以特定方式組織代碼的配方, 你還可以使用它們來和其他開發者讨論模式所解決的問題。

橋接模式是一種結構型設計模式, 可将一個大類或一系列緊密相關的類拆分為抽象和實作兩個獨立的層次結構, 進而能在開發時分别使用。

設計模式-結構型模式-超級棒的例子! GoF

如果你想要拆分或重組一個具有多重功能的龐雜類 (例如能與多個資料庫伺服器進行互動的類), 可以使用橋接模式。

如果你希望在幾個獨立次元上擴充一個類, 可使用該模式。

如果你需要在運作時切換不同實作方法, 可使用橋接模式。

使用示例: 橋接模式在處理跨平台應用、 支援多種類型的資料庫伺服器或與多個特定種類 (例如雲平台和社交網絡等) 的 API 供應商協作時會特别有用。

識别方法: 橋接可以通過一些控制實體及其所依賴的多個不同平台之間的明确差別來進行識别。

裝置和遠端控制之間的橋接

本例展示了遠端控制器及其所控制的裝置的類之間的分離。

遠端控制器是抽象部分, 裝置則是其實作部分。 由于有通用的接口, 同一遠端控制器可與不同的裝置合作, 反過來也一樣。

橋接模式允許在不改動另一層次代碼的前提下修改已有類, 甚至建立新類。

devices

devices/Device.java: 所有裝置的通用接口

devices/Radio.java: 收音機

devices/Tv.java: 電視機

remotes

remotes/Remote.java: 所有遠端控制器的通用接口

remotes/BasicRemote.java: 基礎遠端控制器

remotes/AdvancedRemote.java: 進階遠端控制器

你可以将抽象工廠模式和橋接搭配使用。 如果由橋接定義的抽象隻能與特定實作合作, 這一模式搭配就非常有用。 在這種情況下, 抽象工廠可以對這些關系進行封裝, 并且對用戶端代碼隐藏其複雜性。

你可以結合使用生成器模式和橋接模式: 主管類負責抽象工作, 各種不同的生成器負責實作工作。

組合模式是一種結構型設計模式, 你可以使用它将對象組合成樹狀結構, 并且能像使用獨立對象一樣使用它們。

設計模式-結構型模式-超級棒的例子! GoF

該方式的最大優點在于你無需了解構成樹狀結構的對象的具體類。 你也無需了解對象是簡單的産品還是複雜的盒子。 你隻需調用通用接口以相同的方式對其進行處理即可。 當你調用該方法後, 對象會将請求沿着樹結構傳遞下去。

如果你需要實作樹狀對象結構, 可以使用組合模式。

如果你希望用戶端代碼以相同方式處理簡單和複雜元素, 可以使用該模式 。

使用執行個體: 組合模式在 Java 代碼中很常見,常用于表示與圖形打交道的使用者界面元件或代碼的層次結構。

下面是一些來自 Java 标準程式庫中的組合示例:

<code>java.awt.Container#add(Component)</code> (幾乎廣泛存在于 Swing 元件中)

<code>javax.faces.component.UIComponent#getChildren()</code> (幾乎廣泛存在于 JSF UI 元件中)

識别方法: 組合可以通過将同一抽象或接口類型的執行個體放入樹狀結構的行為方法來輕松識别。

簡單和複合圖形

本例展示了如何利用較為簡單的形狀來組成複雜圖形, 以及如何統一處理簡單和複雜圖形。

shapes

shapes/Shape.java: 通用形狀接口

shapes/BaseShape.java: 提供基本功能的抽象形狀

shapes/Dot.java: 點

shapes/Circle.java: 圓形

shapes/Rectangle.java: 三角形

shapes/CompoundShape.java: 由其他形狀對象組成的複合形狀

editor

editor/ImageEditor.java: 形狀編輯器

橋接模式、 狀态模式和政策模式 (在某種程度上包括擴充卡模式) 模式的接口非常相似。 實際上, 它們都基于組合模式——即将工作委派給其他對象, 不過也各自解決了不同的問題。 模式并不隻是以特定方式組織代碼的配方, 你還可以使用它們來和其他開發者讨論模式所解決的問題。

你可以在建立複雜組合樹時使用生成器模式, 因為這可使其構造步驟以遞歸的方式運作。

責任鍊模式通常群組合模式結合使用。 在這種情況下, 葉元件接收到請求後, 可以将請求沿包含全體父元件的鍊一直傳遞至對象樹的底部。

你可以使用疊代器模式來周遊組合樹。

你可以使用通路者模式對整個組合樹執行操作。

你可以使用享元模式實作組合樹的共享葉節點以節省記憶體。

組合和裝飾模式的結構圖很相似, 因為兩者都依賴遞歸組合來組織無限數量的對象。

裝飾類似于組合, 但其隻有一個子元件。 此外還有一個明顯不同: 裝飾為被封裝對象添加了額外的職責, 組合僅對其子節點的結果進行了 “求和”。

但是, 模式也可以互相合作: 你可以使用裝飾來擴充組合樹中特定對象的行為。

大量使用組合和裝飾的設計通常可從對于原型模式的使用中獲益。 你可以通過該模式來複制複雜結構, 而非從零開始重新構造。

裝飾模式是一種結構型設計模式, 允許你通過将對象放入包含行為的特殊封裝對象中來為原對象綁定新的行為。

設計模式-結構型模式-超級棒的例子! GoF
設計模式-結構型模式-超級棒的例子! GoF

如果你希望在無需修改代碼的情況下即可使用對象, 且希望在運作時為對象新增額外的行為, 可以使用裝飾模式。

裝飾能将業務邏輯組織為層次結構, 你可為各層建立一個裝飾, 在運作時将各種不同邏輯組合成對象。 由于這些對象都遵循通用接口, 用戶端代碼能以相同的方式使用這些對象。

如果用繼承來擴充對象行為的方案難以實作或者根本不可行, 你可以使用該模式。

許多程式設計語言使用 <code>final</code>最終關鍵字來限制對某個類的進一步擴充。 複用最終類已有行為的唯一方法是使用裝飾模式: 用封裝器對其進行封裝。

使用示例: 裝飾在 Java 代碼中可謂是标準配置, 尤其是在與流式加載相關的代碼中。

Java 核心程式庫中有一些關于裝飾的示例:

<code>java.io.InputStream</code>、 <code>Output­Stream</code>、 <code>Reader</code> 和 <code>Writer</code> 的所有代碼都有以自身類型的對象作為參數的構造函數。

<code>java.util.Collections</code>; <code>checked­XXX()</code>、 <code>synchronized­XXX()</code> 和 <code>unmodifiable­XXX()</code> 方法。

<code>javax.servlet.http.HttpServletRequestWrapper</code> 和 <code>Http­Servlet­Response­Wrapper</code>

識别方法: 裝飾可通過以目前類或對象為參數的建立方法或構造函數來識别。

編碼和壓縮裝飾

本例展示了如何在不更改對象代碼的情況下調整其行為。

最初的業務邏輯類僅能讀取和寫入純文字的資料。 此後, 我們建立了幾個小的封裝器類, 以便在執行标準操作後添加新的行為。

第一個封裝器負責加密和解密資料, 而第二個則負責壓縮和解壓資料。

你甚至可以讓這些封裝器嵌套封裝以将它們組合起來。

decorators

decorators/DataSource.java: 定義了讀取和寫入操作的通用資料接口

decorators/FileDataSource.java: 簡單資料讀寫器

decorators/DataSourceDecorator.java: 抽象基礎裝飾

decorators/EncryptionDecorator.java: 加密裝飾

decorators/CompressionDecorator.java: 壓縮裝飾

擴充卡模式可以對已有對象的接口進行修改, 裝飾模式則能在不改變對象接口的前提下強化對象功能。 此外, 裝飾 還支援遞歸組合, 擴充卡 則無法實作。

責任鍊模式和裝飾模式的類結構非常相似。 兩者都依賴遞歸組合将需要執行的操作傳遞給一系列對象。 但是, 兩者有幾點重要的不同之處。

責任鍊的管理者可以互相獨立地執行一切操作, 還可以随時停止傳遞請求。 另一方面, 各種裝飾 可以在遵循基本接口的情況下擴充對象的行為。 此外, 裝飾無法中斷請求的傳遞。

組合模式和裝飾的結構圖很相似, 因為兩者都依賴遞歸組合來組織無限數量的對象。

裝飾可讓你更改對象的外表, 政策模式則讓你能夠改變其本質。

裝飾和代理有着相似的結構, 但是其意圖卻非常不同。 這兩個模式的建構都基于組合原則, 也就是說一個對象應該将部分工作委派給另一個對象。 兩者之間的不同之處在于代理 通常自行管理其服務對象的生命周期, 而裝飾 的生成則總是由用戶端進行控制。

外觀模式是一種結構型設計模式, 能為程式庫、 架構或其他複雜類提供一個簡單的接口。

問題:

假設你必須在代碼中使用某個複雜的庫或架構中的衆多對象。 正常情況下, 你需要負責所有對象的初始化工作、 管理其依賴關系并按正确的順序執行方法等。

最終, 程式中類的業務邏輯将與第三方類的實作細節緊密耦合, 使得了解和維護代碼的工作很難進行。

解決:

外觀類為包含許多活動部件的複雜子系統提供一個簡單的接口。 與直接調用子系統相比, 外觀提供的功能可能比較有限, 但它卻包含了用戶端真正關心的功能。

如果你的程式需要與包含幾十種功能的複雜庫整合, 但隻需使用其中非常少的功能, 那麼使用外觀模式會非常友善,

例如, 上傳貓咪搞笑短視訊到社交媒體網站的應用可能會用到專業的視訊轉換庫, 但它隻需使用一個包含 <code>encode­(filename, format)</code>方法 (以檔案名與檔案格式為參數進行編碼的方法) 的類即可。 在建立這個類并将其連接配接到視訊轉換庫後, 你就擁有了自己的第一個外觀。

與真實世界的類比:

設計模式-結構型模式-超級棒的例子! GoF

你通過手機網購時, 該商店的所有服務和部門的外觀。 程式員為你提供了一個同購物系統、 支付網關和各種送貨服務進行互動的簡單語音接口。

如果你需要一個指向複雜子系統的直接接口, 且該接口的功能有限, 則可以使用外觀模式。

子系統通常會随着時間的推進變得越來越複雜。 即便是應用了設計模式, 通常你也會建立更多的類。 盡管在多種情形中子系統可能是更靈活或易于複用的, 但其所需的配置和樣闆代碼數量将會增長得更快。 為了解決這個問題, 外觀将會提供指向子系統中最常用功能的快捷方式, 能夠滿足用戶端的大部分需求。

如果需要将子系統組織為多層結構, 可以使用外觀。

建立外觀來定義子系統中各層次的入口。 你可以要求子系統僅使用外觀來進行互動, 以減少子系統之間的耦合。

讓我們回到視訊轉換架構的例子。 該架構可以拆分為兩個層次: 音頻相關和視訊相關。 你可以為每個層次建立一個外觀, 然後要求各層的類必須通過這些外觀進行互動。 這種方式看上去與中介者模式非常相似。

下面是一些核心 Java 程式庫中的外觀示例:

<code>javax.faces.context.FacesContext</code> 在底層使用了 <code>Life­Cycle</code>、 <code>View­Handler</code> 和 <code>Navigation­Handler</code> 這幾個類, 但絕大多數用戶端不知道。

<code>javax.faces.context.ExternalContext</code> 在内部使用了 <code>Servlet­Context</code>、 <code>Http­Session</code>、 <code>Http­Servlet­Request</code>、 <code>Http­Servlet­Response</code> 和其他一些類。

識别方法: 外觀可以通過使用簡單接口, 但将絕大部分工作委派給其他類的類來識别。 通常情況下, 外觀管理着其所使用的對象的完整生命周期。

複雜視訊轉換庫的簡單接口

some_complex_media_library/VideoFile.java

some_complex_media_library/Codec.java

some_complex_media_library/MPEG4CompressionCodec.java

some_complex_media_library/OggCompressionCodec.java

some_complex_media_library/CodecFactory.java

some_complex_media_library/BitrateReader.java

some_complex_media_library/AudioMixer.java

facade

facade/VideoConversionFacade.java: 外觀提供了進行視訊轉換的簡單接口

外觀模式為現有對象定義了一個新接口, 擴充卡模式則會試圖運用已有的接口。 擴充卡 通常隻封裝一個對象, 外觀 通常會作用于整個對象子系統上。

當隻需對用戶端代碼隐藏子系統建立對象的方式時, 你可以使用抽象工廠模式來代替外觀。

享元模式展示了如何生成大量的小型對象, 外觀則展示了如何用一個對象來代表整個子系統。

外觀和中介者模式的職責類似: 它們都嘗試在大量緊密耦合的類中組織起合作。

外觀 為子系統中的所有對象定義了一個簡單接口, 但是它不提供任何新功能。 子系統本身不會意識到外觀的存在。 子系統中的對象可以直接進行交流。

中介者 将系統中元件的溝通行為中心化。 各元件隻知道中介者對象, 無法直接互相交流。

外觀類通常可以轉換為單例模式類, 因為在大部分情況下一個外觀對象就足夠了。

外觀與代理模式的相似之處在于它們都緩存了一個複雜實體并自行對其進行初始化。 代理 與其服務對象遵循同一接口, 使得自己和服務對象可以互換, 在這一點上它與外觀 不同。

享元模式是一種結構型設計模式, 它摒棄了在每個對象中儲存所有資料的方式, 通過共享多個對象所共有的相同狀态, 讓你能在有限的記憶體容量中載入更多對象。

享元模式通過共享多個對象的部分狀态來實作上述功能。 換句話來說, 享元會将不同對象的相同資料進行緩存以節省記憶體。

設計模式-結構型模式-超級棒的例子! GoF

問題

開發了一款簡單的遊戲: 玩家們在地圖上移動并互相射擊。 你決定實作一個真實的粒子系統, 并将其作為遊戲的特色。 大量的子彈、 飛彈和爆炸彈片會在整個地圖上穿行, 為玩家提供緊張刺激的遊戲體驗。

遊戲總是會在他的電腦上運作幾分鐘後崩潰。 在研究了幾個小時的調試消息記錄後, 你發現導緻遊戲崩潰的原因是記憶體容量不足。 真正的問題與粒子系統有關。 每個粒子 (一顆子彈、 一枚飛彈或一塊彈片) 都由包含完整資料的獨立對象來表示。 當玩家在遊戲中鏖戰進入高潮後的某一時刻, 遊戲将無法在剩餘記憶體中載入建立粒子, 于是程式就崩潰了。

解決方案

每個粒子的隻有一些狀态,例如: (坐标、 移動矢量和速度) 是不同的。

對象的常量資料通常被稱為内在狀态, 其位于對象中, 其他對象隻能讀取但不能修改其數值。 而對象的其他狀态常常能被其他對象 “從外部” 改變, 是以被稱為外在狀态。

享元模式建議不在對象中存儲外在狀态, 而是将其傳遞給依賴于它的一個特殊方法。 程式隻在對象中儲存内在狀态, 以友善在不同情景下重用。 這些對象的差別僅在于其内在狀态 (與外在狀态相比, 内在狀态的變體要少很多), 是以你所需的對象數量會大大削減。

外在狀态存儲

為了能将外在狀态移動到這個類中, 你需要建立多個數組成員變量來存儲每個粒子的坐标、 方向矢量和速度。 除此之外, 你還需要另一個數組來存儲指向代表粒子的特定享元的引用。 這些數組必須保持同步, 這樣你才能夠使用同一索引來擷取關于某個粒子的所有資料。

更優雅的解決方案是建立獨立的情景類來存儲外在狀态和對享元對象的引用。 在該方法中, 容器類隻需包含一個數組。

這樣的話情景對象數量不是會和不采用該模式時的對象數量一樣多嗎? 的确如此, 但這些對象要比之前小很多。 消耗記憶體最多的成員變量已經被移動到很少的幾個享元對象中了。 現在, 一個享元大對象會被上千個情境小對象複用, 是以無需再重複存儲數千個大對象的資料。

享元與不可變性

由于享元對象可在不同的情景中使用, 你必須確定其狀态不能被修改。 享元類的狀态隻能由構造函數的參數進行一次性初始化, 它不能對其他對象公開其設定器或公有成員變量。

享元工廠

為了能更友善地通路各種享元, 你可以建立一個工廠方法來管理已有享元對象的緩存池。 工廠方法從用戶端處接收目标享元對象的内在狀态作為參數, 如果它能在緩存池中找到所需享元, 則将其傳回給用戶端; 如果沒有找到, 它就會建立一個享元, 并将其添加到緩存池中。

你可以選擇在程式的不同地方放入該函數。 最簡單的選擇就是将其放置在享元容器中。 除此之外, 你還可以建立一個工廠類, 或者建立一個靜态的工廠方法并将其放入實際的享元類中。

僅在程式必須支援大量對象且沒有足夠的記憶體容量時使用享元模式。

應用該模式所獲的收益大小取決于使用它的方式和情景。 它在下列情況中最有效:

程式需要生成數量巨大的相似對象

這将耗盡目标裝置的所有記憶體

對象中包含可抽取且能在多個對象間共享的重複狀态。

享元模式在核心 Java 程式庫中的示例:

<code>java.lang.Integer#valueOf(int)</code> (以及 <code>Boolean</code>、 <code>Byte</code>、 <code>Character</code>、 <code>Short</code>、 <code>Long</code> 和 <code>Big­Decimal</code>)

識别方法: 享元可以通過建構方法來識别, 它會傳回緩存對象而不是建立新的對象。

渲染一片森林

本例中, 我們将渲染一片森林 (1,000,000 棵樹)! 每棵樹都由包含一些狀态的對象來表示 (坐标和紋理等)。 盡管程式能夠完成其主要工作, 但很顯然它需要消耗大量記憶體。

原因很簡單: 太多樹對象包含重複資料 (名稱、 紋理和顔色)。 是以我們可用享元模式來将這些數值存儲在單獨的享元對象中 ( <code>Tree­Type</code>類)。 現在我們不再将相同資料存儲在數千個 <code>Tree</code>對象中, 而是使用一組特殊的數值來引用其中一個享元對象。

用戶端代碼不會知道任何事情, 因為重用享元對象的複雜機制隐藏在了享元工廠中。

trees

trees/Tree.java: 包含每棵樹的獨特狀态

trees/TreeType.java: 包含多棵樹共享的狀态

trees/TreeFactory.java: 封裝建立享元的複雜機制

forest

forest/Forest.java: 我們繪制的森林

你可以使用享元模式實作組合模式樹的共享葉節點以節省記憶體。

享元展示了如何生成大量的小型對象, 外觀模式則展示了如何用一個對象來代表整個子系統。

如果你能将對象的所有共享狀态簡化為一個享元對象, 那麼享元就和單例模式類似了。 但這兩個模式有兩個根本性的不同。

隻會有一個單例實體, 但是享元類可以有多個實體, 各實體的内在狀态也可以不同。

單例 對象可以是可變的。 享元對象是不可變的。

代理模式是一種結構型設計模式, 讓你能夠提供對象的替代品或其占位符。 代理控制着對于原對象的通路, 并允許在将請求送出給對象前後進行一些處理。

代理是一種結構型設計模式, 讓你能提供真實服務對象的替代品給用戶端使用。 代理接收用戶端的請求并進行一些處理 (通路控制和緩存等), 然後再将請求傳遞給服務對象。

代理對象擁有和服務對象相同的接口, 這使得當其被傳遞給用戶端時可與真實對象互換。

設計模式-結構型模式-超級棒的例子! GoF

于真實世界類比

設計模式-結構型模式-超級棒的例子! GoF

信用卡是銀行賬戶的代理, 銀行賬戶則是一大捆現金的代理。

它們都實作了同樣的接口, 均可用于進行支付。 消費者會非常滿意, 因為不必随身攜帶大量現金; 商店老闆同樣會十分高興, 因為交易收入能以電子化的方式進入商店的銀行賬戶中, 無需擔心存款時出現現金丢失或被搶劫的情況。

延遲初始化 (虛拟代理)。 如果你有一個偶爾使用的重量級服務對象, 一直保持該對象運作會消耗系統資源時, 可使用代理模式。

你無需在程式啟動時就建立該對象, 可将對象的初始化延遲到真正有需要的時候。

通路控制 (保護代理)。 如果你隻希望特定用戶端使用服務對象, 這裡的對象可以是作業系統中非常重要的部分, 而用戶端則是各種已啟動的程式 (包括惡意程式), 此時可使用代理模式。

代理可僅在用戶端憑據滿足要求時将請求傳遞給服務對象。

本地執行遠端服務 (遠端代理)。 适用于服務對象位于遠端伺服器上的情形。

這種情形中, 代理通過網絡傳遞用戶端請求, 負責處理所有與網絡相關的複雜細節。

記錄日志請求 (日志記錄代理)。 适用于當你需要儲存對于服務對象的請求曆史記錄時。 代理可以在向服務傳遞請求前進行記錄。

緩存請求結果 (緩存代理)。 适用于需要緩存客戶請求結果并對緩存生命周期進行管理時, 特别是當傳回結果的體積非常大時。

代理可對重複請求所需的相同結果進行緩存, 還可使用請求參數作為索引緩存的鍵值。

使用示例: 盡管代理模式在絕大多數 Java 程式中并不常見, 但它在一些特殊情況下仍然非常友善。 當你希望在無需修改客戶代碼的前提下于已有類的對象上增加額外行為時, 該模式是無可替代的。

Java 标準程式庫中的一些代理模式的示例:

<code>java.lang.reflect.Proxy</code>

<code>java.rmi.*</code>

<code>javax.ejb.EJB</code> (檢視評論)

<code>javax.inject.Inject</code> (檢視評論)

<code>javax.persistence.PersistenceContext</code>

識别方法: 代理模式會将所有實際工作委派給一些其他對象。 除非代理是某個服務的子類, 否則每個代理方法最後都應該引用一個服務對象。

緩存代理

在本例中, 代理模式有助于實作延遲初始化, 并對低效的第三方 YouTube 內建程式庫進行緩存。

當你需要在無法修改代碼的類上新增一些額外行為時, 代理模式的價值無可估量。

some_cool_media_library

some_cool_media_library/ThirdPartyYouTubeLib.java: 遠端服務接口

some_cool_media_library/ThirdPartyYouTubeClass.java: 遠端服務實作

some_cool_media_library/Video.java: 視訊檔案

proxy

proxy/YouTubeCacheProxy.java: 緩存代理

downloader

downloader/YouTubeDownloader.java: 媒體下載下傳應用

Demo.java: 初始化代碼

擴充卡模式能為被封裝對象提供不同的接口, 代理模式能為對象提供相同的接口, 裝飾模式則能為對象提供加強的接口。

外觀模式與代理的相似之處在于它們都緩存了一個複雜實體并自行對其進行初始化。 代理 與其服務對象遵循同一接口, 使得自己和服務對象可以互換, 在這一點上它與外觀 不同。

裝飾和代理有着相似的結構, 但是其意圖卻非常不同。 這兩個模式的建構都基于組合原則, 也就是說一個對象應該将部分工作委派給另一個對象。 兩者之間的不同之處在于代理通常自行管理其服務對象的生命周期, 而裝飾的生成則總是由用戶端進行控制。

繼續閱讀