定義
外觀(Facade)模式是一種通過爲多個複雜的子系統提供一個統一的接口,而使這些子系統更加容易被訪問的模式。該模式對外有一個統一接口,外部應用程序不用關心內部子系統的具體的細節,這樣會大大降低應用程序的複雜度,提高了程序的可維護性。
外觀模式屬於對象結構型模式。
要點
外觀(Facade)模式是“迪米特法則”的典型應用,優點:
- 降低了子系統與客戶端之間的耦合度,使得子系統的變化不會影響調用它的客戶類。
- 對客戶屏蔽了子系統組件,減少了客戶處理的對象數目,並使得子系統使用起來更加容易。
- 降低了大型軟件系統中的編譯依賴性,簡化了系統在不同平臺之間的移植過程,因爲編譯一個子系統不會影響其他的子系統,也不會影響外觀對象。
外觀(Facade)模式的缺點:
- 不能很好地限制客戶使用子系統類。
- 增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
外觀(Facade)模式包含以下主要角色:
外觀(Facade):爲多個子系統對外提供一個共同的接口。
子系統(Sub System):實現系統的部分功能,客戶可以通過外觀角色訪問它。
客戶(Client):通過一個外觀角色訪問各個子系統的功能。
場景
組建一個家庭影院,DVD 播放器、投影儀、自動屏幕、環繞立體聲、要求完成使用家庭影院的功能:
- 放下屏幕
- 開投影儀
- 開音響
- 開DVD
- 調暗燈光
- 觀影結束後,關閉各種設備
其過程爲: 直接用遙控器統籌各設備開關,簡化用戶操作步驟。
實現
Screen
/**
* 電影幕布
*/
public class Screen {
public void up() {
System.out.println("升起電影幕布...");
}
public void down() {
System.out.println("放下電影幕布...");
}
}
Projector
/**
* 投影儀
*/
public class Projector {
public void on() {
System.out.println("打開投影儀...");
}
public void off() {
System.out.println("關閉投影儀...");
}
public void focus() {
System.out.println("調節投影儀焦距...");
}
}
Audio
/**
* 音響
*/
public class Audio {
public void on() {
System.out.println("打開音響...");
}
public void off() {
System.out.println("關閉音響...");
}
public void setVolume() {
System.out.println("設置音量...");
}
}
DVD
/**
* DVD播放器
*/
public class DVD {
public void on() {
System.out.println("打開DVD播放器...");
}
public void off() {
System.out.println("關閉DVD播放器...");
}
public void pause() {
System.out.println("DVD播放暫停...");
}
public void play() {
System.out.println("DVD開始播放...");
}
public void setDisk(String movieName) {
System.out.println(String.format("指定DVD影碟:[ %s ]", movieName));
}
}
Light
/**
* 燈光
*/
public class Light {
public void turnUp() {
System.out.println("調亮燈光...");
}
public void turnDown() {
System.out.println("調暗燈光...");
}
}
CinemaRemoteControlFacade
/**
* 影院集成遙控控制(外觀角色)
*/
public class CinemaRemoteControlFacade {
private Screen screen;
private Projector projector;
private Audio audio;
private DVD dvd;
private Light light;
public CinemaRemoteControlFacade(Screen screen, Projector projector, Audio audio, DVD dvd, Light light) {
this.screen = screen;
this.projector = projector;
this.audio = audio;
this.dvd = dvd;
this.light = light;
}
/**
* 開始看電影
*/
public void watchMovie(String movieName) {
screen.down();
projector.on();
projector.focus();
audio.on();
audio.setVolume();
dvd.on();
dvd.setDisk(movieName);
dvd.play();
light.turnDown();
}
/**
* 結束看電影
*/
public void endMovie() {
dvd.off();
audio.off();
projector.off();
light.turnUp();
screen.up();
}
}
Client
public class Client {
public static void main(String[] args) {
CinemaRemoteControlFacade remoteControl = new CinemaRemoteControlFacade(new Screen(), new Projector(), new Audio(), new DVD(), new Light());
System.out.println("------------準備播放電影------------\n");
remoteControl.watchMovie("決戰中途島");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n------------電影播放結束------------\n");
remoteControl.endMovie();
}
}
==================================
輸出:
------------準備播放電影------------
放下電影幕布...
打開投影儀...
調節投影儀焦距...
打開音響....
設置音量...
打開DVD播放器...
指定DVD影碟:[ 決戰中途島 ]
DVD開始播放...
調暗燈光...
------------播放結束------------
關閉DVD播放器...
關閉音響...
關閉投影儀...
調亮燈光...
升起電影幕布...
源碼
總結
- 外觀模式對外屏蔽了子系統的細節,因此外觀模式降低了客戶端對子系統使用的複雜性
- 外觀模式對客戶端與子系統的耦合關係 - 解耦,讓子系統內部的模塊更易維護和擴展
- 通過合理的使用外觀模式,可以幫我們更好的劃分訪問的層次
- 當系統需要進行分層設計時,可以考慮使用 Facade 模式
- 在維護一個遺留的大型系統時,可能這個系統已經變得非常難以維護和擴展,此時可以考慮爲新系統開發一個 Facade 類,來提供遺留系統的比較清晰簡單的接口,讓新系統與 Facade 類交互,提高複用性
- 不能過多的或者不合理的使用外觀模式,使用外觀模式好,還是直接調用模塊好。要以讓系統有層次,利於維護爲目的。
在外觀模式中,當增加或移除子系統時需要修改外觀類,這違背了“開閉原則”。如果引入抽象外觀類,讓具體外觀類繼承抽象外觀類,具體外觀類聚合使用需要的子系統即可。
Tomcat中有很多場景都使用到了外觀模式,因爲Tomcat中有很多不同的組件,每個組件需要相互通信,但又不能將自己內部數據過多地暴露給其他組件。用外觀模式隔離數據是個很好的方法,比如Request上使用外觀模式,因爲Request類中很多方法都是組件內部之間交互用的,比如setComet、setReuqestedSessionId等方法,這些方法並不對外公開,但又必須設置爲public,因爲還要和內部組件交互使用。最好的解決方法就是通過使用一個Facade類,屏蔽掉內部組件之間交互的方法,只提供外部程序要使用的方法。