外觀模式(學習筆記)
1. 意圖
為子系統中的一組介面提供一個一致的介面,Facade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用
2. 動機
將一個系統劃分成若干個子系統有利於降低系統的複雜性。一個常見的設計目標是使子系統間的通訊和相互依賴關係達到最小。達到該目標的途徑之一就是引入一個外觀物件,他為子系統中較一般的設施提供了一個單一而簡單的介面
例如有一個程式設計環境,它允許應用程式訪問它的編譯子系統。這個編譯子系統包含了若干個類來實現這一編譯器,如Scanner,Parser,ProgramNode,BytecodeStream和ProgramNodeBuilder。但是對於大多數編譯器的使用者並不關心語法分析和程式碼生成這樣的細節,他們只是希望編譯一些程式碼。編譯子系統提供了一個Compiler類,這個類定義了一個編譯器功能的統一介面。Compiler類是一個外觀,它給使用者提供了一個單一而簡單的編譯子系統介面。編譯器的外觀可以方便大多數程式設計師使用,同時對少數懂得如何使用底層功能的人,它並不隱藏這些功能
3. 適用性
- 當要為一個複雜子系統提供一個簡單介面時。子系統往往因為不斷演化而變得越來越複雜,大多數模式使用時都會產生更多更小的類。這使得子系統更具有可複用性,也更容易對子系統進行定製,但也給一些不需要定製子系統的使用者帶來一些使用上的困難。Facade可以提供一個簡單的預設檢視,以方便大多數使用者的使用
- 客戶程式與抽象類的實現部分之間存在著很大的依賴性。引入Facade將這個子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移植性
- 當需要構建一個層次結構的子系統時,使用Facade模式定義子系統中每層的入口點。如果子系統之間是相互依賴的,可以讓它們僅通過Facade進行通訊,從而簡化了它們之間的依賴關係(有點類似中介者模式)
4. 結構
5. 效果
1) 它對客戶遮蔽子系統元件,因而減少了客戶處理的物件數目並使得子系統使用起來更加方便
2) 實現了子系統和客戶之間的鬆耦合關係,而子系統內部的功能元件往往是緊耦合的。鬆耦合關係使得子系統的元件變化不會影響到它的客戶。Facade模式有助於建立層次結構系統,也有助於對物件之間的依賴關係分層。Facade模式可以消除複雜的迴圈依賴關係
3) 如果需要,該模式並不限制使用子系統類
6. 程式碼實現
some_complex_media_library/VideoFile.java
package facade.some_complex_media_library;/** * @author GaoMing * @date 2021/7/19 - 20:59 */ public class VideoFile { private String name; private String codecType; public VideoFile(String name) { this.name = name; this.codecType = name.substring(name.indexOf(".") + 1); } public String getCodecType() { return codecType; } public String getName() { return name; } }
some_complex_media_library/Codec.java
package facade.some_complex_media_library; /** * @author GaoMing * @date 2021/7/19 - 20:59 */ public interface Codec { }
some_complex_media_library/MPEG4CompressionCodec.java
package facade.some_complex_media_library; /** * @author GaoMing * @date 2021/7/19 - 21:00 */ public class MPEG4CompressionCodec implements Codec{ public String type = "mp4"; }
some_complex_media_library/OggCompressionCodec.java
package facade.some_complex_media_library; /** * @author GaoMing * @date 2021/7/19 - 21:00 */ public class OggCompressionCodec implements Codec{ public String type = "ogg"; }
some_complex_media_library/CodecFactory.java
package facade.some_complex_media_library; /** * @author GaoMing * @date 2021/7/19 - 21:01 */ public class CodecFactory { public static Codec extract(VideoFile file) { String type = file.getCodecType(); if (type.equals("mp4")) { System.out.println("CodecFactory: extracting mpeg audio..."); return new MPEG4CompressionCodec(); } else { System.out.println("CodecFactory: extracting ogg audio..."); return new OggCompressionCodec(); } } }
some_complex_media_library/BitrateReader.java
package facade.some_complex_media_library; /** * @author GaoMing * @date 2021/7/19 - 21:01 */ public class BitrateReader { public static VideoFile read(VideoFile file, Codec codec) { System.out.println("BitrateReader: reading file..."); return file; } public static VideoFile convert(VideoFile buffer, Codec codec) { System.out.println("BitrateReader: writing file..."); return buffer; } }
some_complex_media_library/AudioMixer.java
package facade.some_complex_media_library; import java.io.File; /** * @author GaoMing * @date 2021/7/19 - 21:01 */ public class AudioMixer { public File fix(VideoFile result){ System.out.println("AudioMixer: fixing audio..."); return new File("tmp"); } }
facade/VideoConversionFacade.java: 外觀提供了進行視訊轉換的簡單介面
package facade.facade; import facade.some_complex_media_library.*; import java.io.File; /** * @author GaoMing * @date 2021/7/19 - 21:02 */ public class VideoConversionFacade { public File convertVideo(String fileName, String format) { System.out.println("VideoConversionFacade: conversion started."); VideoFile file = new VideoFile(fileName); Codec sourceCodec = CodecFactory.extract(file); Codec destinationCodec; if (format.equals("mp4")) { destinationCodec = new OggCompressionCodec(); } else { destinationCodec = new MPEG4CompressionCodec(); } VideoFile buffer = BitrateReader.read(file, sourceCodec); VideoFile intermediateResult = BitrateReader.convert(buffer, destinationCodec); File result = (new AudioMixer()).fix(intermediateResult); System.out.println("VideoConversionFacade: conversion completed."); return result; } }
Demo.java: 客戶端程式碼
package facade; import facade.facade.VideoConversionFacade; import java.io.File; /** * @author GaoMing * @date 2021/7/19 - 20:59 */ public class Demo { public static void main(String[] args) { VideoConversionFacade converter = new VideoConversionFacade(); File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4"); // ... } }
執行結果
VideoConversionFacade: conversion started.
CodecFactory: extracting ogg audio...
BitrateReader: reading file...
BitrateReader: writing file...
AudioMixer: fixing audio...
VideoConversionFacade: conversion completed.
7. 與其他模式的關係
- 外觀模式為現有物件定義了一個新介面,介面卡模式則會試圖運用已有的介面。介面卡通常只封裝一個物件,外觀通常會作用於整個物件子系統上
- 當只需對客戶端程式碼隱藏子系統建立物件的方式時, 你可以使用抽象工廠模式來代替外觀
- 享元模式展示瞭如何生成大量的小型物件, 外觀則展示瞭如何用一個物件來代表整個子系統
-
外觀和中介者模式的職責類似:它們都嘗試在大量緊密耦合的類中組織起合作
- 外觀為子系統中的所有物件定義了一個簡單介面, 但是它不提供任何新功能。子系統本身不會意識到外觀的存在。子系統中的物件可以直接進行交流
- 中介者將系統中元件的溝通行為中心化。各元件只知道中介者物件, 無法直接相互交流 - 外觀類通常可以轉換為單例模式類,因為在大部分情況下一個外觀物件就足夠了
- 外觀與代理模式的相似之處在於它們都快取了一個複雜實體並自行對其進行初始化。代理與其服務物件遵循同一介面,使得自己和服務物件可以互換,在這一點上它與外觀不同
8. 已知應用
- javax.faces.context.FacesContext 在底層使用了 LifeCycle、ViewHandler 和 NavigationHandler 這幾個類,但絕大多數客戶端不知道
- javax.faces.context.ExternalContext 在內部使用了 ServletContext、HttpSession、HttpServletRequest、HttpServletResponse 和其他一些類
識別方法:外觀可以通過使用簡單介面,但將絕大部分工作委派給其他類的類來識別。通常情況下,外觀管理著其所使用的物件的完整生命週期