定義
外觀模式(facade pattern)提供了一個統一的接口,用來訪問子系統中的一羣接口。外觀定義了一個高層接口,讓子系統更加容易使用。
從上面的圖可以看出,Facade類對子系統進行了一下封裝,客戶只需要和Facade類打交道,不需要接觸子系統中的各個類,也不需要了解子系統中各個類間的關係。從這個角度來說,客戶也就與子系統解耦了,不需要依賴於子系統中具體的類了。
外觀模式的一個重要的目的就是簡化系統,提供給客戶一個簡單的接口,方便客戶使用。
代碼實現
例子是爲家庭影院構造外觀,讓觀看電影的步驟儘可能地簡單。下圖給出了類圖。
首先看看HomeTheaterFacade這個高層接口,它提供了6個接口分別執行6個動作,每個動作是通過調用子系統中其它的類來實現的。
public class HomeTheaterFacade {
Amplifier amp; // 功放
Tuner tuner; // 電子調音器
DvdPlayer dvd; //
CdPlayer cd; //
Projector projector; // 投影機
TheaterLights lights; // 劇場燈光
Screen screen; // 屏幕
PopcornPopper popper; // 爆米花機
public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd,
CdPlayer cd, Projector projector, Screen screen,
TheaterLights lights, PopcornPopper popper) {
this.amp = amp;
this.tuner = tuner;
this.dvd = dvd;
this.cd = cd;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}
// 開始觀看名爲“movie”的電影接口
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
popper.on(); // 打開爆米花機
popper.pop(); // 開始爆米花
lights.dim(10); // 調低燈光亮度爲10%
screen.down(); // 放下屏幕
projector.on(); // 打開投影機
projector.wideScreenMode(); // 設置寬屏模式
amp.on(); // 打開功放
amp.setDvd(dvd);
amp.setSurroundSound(); // 設置環繞立體聲
amp.setVolume(5); // 設置音量
dvd.on(); // 打開DVD播放器
dvd.play(movie); // 開始播放電影
}
public void endMovie() {
System.out.println("Shutting movie theater down...");
popper.off();
lights.on();
screen.up();
projector.off();
amp.off();
dvd.stop();
dvd.eject(); // 彈出Dvd播放器
dvd.off();
}
public void listenToCd(String cdTitle) {
System.out.println("Get ready for an audiopile experence...");
lights.on();
amp.on();
amp.setVolume(5);
amp.setCd(cd);
amp.setStereoSound();
cd.on();
cd.play(cdTitle);
}
public void endCd() {
System.out.println("Shutting down CD...");
amp.off();
amp.setCd(cd);
cd.eject();
cd.off();
}
public void listenToRadio(double frequency) {
System.out.println("Tuning in the airwaves...");
tuner.on();
tuner.setFrequency(frequency);
amp.on();
amp.setVolume(5);
amp.setTuner(tuner);
}
public void endRadio() {
System.out.println("Shutting down the tuner...");
tuner.off();
amp.off();
}
}
因爲子系統中類太多了,這裏只列出幾個,其它的類似,不影響理解模式。
Amplifier.java
public class Amplifier {
String description;
Tuner tuner;
DvdPlayer dvd;
CdPlayer cd;
public Amplifier(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " on");
}
public void off() {
System.out.println(description + " off");
}
public void setStereoSound() {
System.out.println(description + " stereo mode on");
}
public void setSurroundSound() {
System.out.println(description + " surround sound on (5 speakers, 1 subwoofer)");
}
public void setVolume(int level) {
System.out.println(description + " setting volume to " + level);
}
public void setTuner(Tuner tuner) {
System.out.println(description + " setting tuner to " + dvd);
this.tuner = tuner;
}
public void setDvd(DvdPlayer dvd) {
System.out.println(description + " setting DVD player to " + dvd);
this.dvd = dvd;
}
public void setCd(CdPlayer cd) {
System.out.println(description + " setting CD player to " + cd);
this.cd = cd;
}
public String toString() {
return description;
}
}
CdPlayer.java
public class CdPlayer {
String description;
int currentTrack;
Amplifier amplifier;
String title;
public CdPlayer(String description, Amplifier amplifier) {
this.description = description;
this.amplifier = amplifier;
}
public void on() {
System.out.println(description + " on");
}
public void off() {
System.out.println(description + " off");
}
public void eject() {
title = null;
System.out.println(description + " eject");
}
public void play(String title) {
this.title = title;
currentTrack = 0;
System.out.println(description + " playing \"" + title + "\"");
}
public void play(int track) {
if (title == null) {
System.out.println(description + " can't play track " + currentTrack +
", no cd inserted");
} else {
currentTrack = track;
System.out.println(description + " playing track " + currentTrack);
}
}
public void stop() {
currentTrack = 0;
System.out.println(description + " stopped");
}
public void pause() {
System.out.println(description + " paused \"" + title + "\"");
}
public String toString() {
return description;
}
}
Screen.java
public class Screen {
String description;
public Screen(String description) {
this.description = description;
}
public void up() {
System.out.println(description + " going up");
}
public void down() {
System.out.println(description + " going down");
}
public String toString() {
return description;
}
}
測試驅動代碼,也是客戶代碼:
public class HomeTheaterTestDrive {
public static void main(String[] args) {
Amplifier amp = new Amplifier("Top-O-Line Amplifier");
Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
Projector projector = new Projector("Top-O-Line Projector", dvd);
TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
Screen screen = new Screen("Theater Screen");
PopcornPopper popper = new PopcornPopper("Popcorn Popper");
HomeTheaterFacade homeTheater =
new HomeTheaterFacade(amp, tuner, dvd, cd,
projector, screen, lights, popper);
// 客戶開始/停止看電影只要調用高層接口的Api即可
// 不需要和子系統中其它類交互
homeTheater.watchMovie("Raiders of the Lost Ark");
homeTheater.endMovie();
}
}
該模式體現了哪些OO原則
-
HomeTheaterFacade組合了子系統中各個對象,來實現對客戶來說更加友好對接口。
-
客戶代碼只要調用HomeTheaterFacade的方法,只依賴於它。不管子系統內部實現怎麼變化,只要HomeTheaterFacade的接口沒有變化,客戶代碼就不需要變化。
本章總結
外觀模式不只是簡化了子系統的接口,也將客戶從組件的子系統中解耦
外觀只是對子系統的接口進行了更高層次的抽象,客戶還是可以直接調用子系統中的類的,當然一般情況這是不必要的,只要外觀接口定義的好
外觀模式符合“最少知識原則”
適配器模式和外觀模式的差別:
兩種模式都是對其它的接口的再次封裝。差別在適配器強調對一個或者多個接口的轉換,而外觀模式則強調簡化接口