實例引入
在家庭影院中,有燈光,屏幕,投影機,功放機,dvd 播放器這幾個基本的工具:
- 燈光,可以關閉燈光和打開燈光。
- 投影機,可以打開和關閉投影機。
- 屏幕,可以打開和關閉。
- 功放機,可以關閉音量和打開音量。
- dvd 播放器,可以打開播放器和關閉播放器。
以最普通的方式實現觀看電影,類圖如下所示:
按照類圖所示,如果要觀看電影,必須在客戶端執行下面的操作:先打開投影儀,再打開功放機,再打開屏幕,再打開 dvd 播放機,再打開燈光,在經歷了這么多操作后,才可以看一場電影。而在關閉電影的時候,也要先關閉投影儀,再關閉功放機,再關閉屏幕,再關閉 dvd 播放機,再關閉燈光。哦,這是太復雜了!!!在客戶端居然有那么多操作,如果有一些用戶不知道如何使用其中的一個工具,那他便看不了電影!
上面其實反映的是現今軟件開發系統中的一個比較常見的現象,客戶端程序經常和復雜系統的內部子系統產生直接聯系,導致客戶程序隨著子系統的變化而變化。要想解決上面的這一串問題,必須要簡化客戶程序與子系統之間的交互接口,解除客戶程序和子系統之間的耦合,而外觀模式正好可以解決這個問題。
外觀模式(facade)的定義:為子系統中的一組接口提供一個一致的界面,用來訪問子系統中的一群接口。
此模式定義了一個高層的接口,這個接口使得這一子系統更加容易使用。簡單的說,就是外觀模式將一個或者多個類的復雜的操作進行了隱藏,只顯示出一個一致的界面供客戶端使用。需要注意的是,外觀模式僅僅是給你提供了更為直接和容易的操作方式,它并沒有把原來的子系統進行隔離,所以,如果你還需要子系統類的更高層的功能,還是可以使用原來的子系統的,這個是外觀模式的一大優點。通過外觀模式可以將子系統的多個接口上建立一個高層接口,并且將這個高層接口提供給客戶端使用,這樣便可以解除掉客戶端和復雜子系統之間的耦合。
通過上圖可以看出,外觀模式實現提供簡單的接口(openmovie 和 closemovie)給客戶端,也給客戶端和子系統之間實現了解耦。下面通過代碼來實現上面的這個 demo。
幾個播放工具的代碼:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
using system; namespace facade { /// <summary> /// 投影儀 /// </summary> public class projector { public void openprojector() { console.writeline( "打開投影儀" ); } public void closeprojector() { console.writeline( "關閉投影儀" ); } public void setwidescreen() { console.writeline( "投影儀狀態為寬屏模式" ); } public void setstandardscreen() { console.writeline( "投影儀狀態為標準模式" ); } } } using system; namespace facade { /// <summary> /// 功放機 /// </summary> public class amplifier { public void openamplifier() { console.writeline( "打開功放機" ); } public void closeamplifier() { console.writeline( "關閉功放機" ); } } } using system; namespace facade { /// <summary> /// 屏幕 /// </summary> public class screen { public void openscreen() { console.writeline( "打開屏幕" ); } public void closescreen() { console.writeline( "關閉屏幕" ); } } } using system; namespace facade { /// <summary> /// dvd播放器 /// </summary> public class dvdplayer { public void opendvdplayer() { console.writeline( "打開 dvd 播放器" ); } public void closedvdplayer() { console.writeline( "關閉 dvd 播放器" ); } } } using system; namespace facade { /// <summary> /// 燈光 /// </summary> public class light { public void openlight() { console.writeline( "打開燈光" ); } public void closelight() { console.writeline( "關閉燈光" ); } } } |
外觀類中的代碼:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
namespace facade { /// <summary> /// 定義一個外觀 /// </summary> public class moviefacade { /// <summary> /// 在外觀類中必須保存有子系統中各個對象 /// </summary> private projector projector; private amplifier amplifier; private screen screen; private dvdplayer dvdplayer; private light light; public moviefacade() { projector = new projector(); amplifier = new amplifier(); screen = new screen(); dvdplayer = new dvdplayer(); light = new light(); } /// <summary> /// 打開電影 /// </summary> public void openmovie() { //先打開投影儀 projector.openprojector(); //再打開功放 amplifier.openamplifier(); //再打開屏幕 screen.openscreen(); //再打開 dvd dvdplayer.opendvdplayer(); //再打開燈光 light.openlight(); } /// <summary> /// 關閉電影 /// </summary> public void closemovie() { //關閉投影儀 projector.closeprojector(); //關閉功放 amplifier.closeamplifier(); //關閉屏幕 screen.closescreen(); //關閉 dvd dvdplayer.closedvdplayer(); //關閉燈光 light.closelight(); } } } |
客戶端代碼:
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
|
using system; namespace facadetest { class program { static void main( string [] args) { facade.moviefacade movie = new facade.moviefacade(); facade.projector projector = new facade.projector(); //首先是觀看電影 movie.openmovie(); console.writeline(); //然后是將投影儀模式調到寬屏模式 projector.setwidescreen(); //再將投影儀模式調回普通模式 projector.setstandardscreen(); console.writeline(); //最后就是關閉電影了 movie.closemovie(); console.readkey(); } } } |
從上例中可以看出,可以在客戶端中使用子系統中的內容,即外觀模式并沒有把子系統和客戶端隔離開來,只是提供了整潔的接口給客戶端,如果客戶端想訪問復雜子系統中的接口時還是一樣的可以訪問的。比如在上面的 demo 中的設置了寬屏和普通等模式。
外觀模式的結構總結
看完外觀模式的實現之后,為了幫助理清外觀模式中類之間的關系,下面給出上面實現代碼中類圖:
然而對于外觀模式而言,是沒有一個一般化的類圖描述,下面演示一個外觀模式的示意性對象圖來加深大家對外觀模式的理解:
在上面的對象圖中有兩個角色:
門面(facade)角色:客戶端調用這個角色的方法。該角色知道相關的一個或多個子系統的功能和責任,該角色會將從客戶端發來的請求委派帶相應的子系統中去。
子系統(subsystem)角色:可以同時包含一個或多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合。每個子系統都可以被客戶端直接調用或被門面角色調用。對于子系統而言,門面僅僅是另外一個客戶端,子系統并不知道門面的存在。
外觀的優缺點
優點:
外觀模式對客戶屏蔽了子系統組件,從而簡化了接口,減少了客戶處理的對象數目并使子系統的使用更加簡單。
外觀模式實現了子系統與客戶之間的松耦合關系,而子系統內部的功能組件是緊耦合的。松耦合使得子系統的組件變化不會影響到它的客戶。
缺點:
如果增加新的子系統可能需要修改外觀類或客戶端的源代碼,這樣就違背了”開——閉原則“(不過這點也是不可避免)。
使用場景
在以下情況下可以考慮使用外觀模式:
外一個復雜的子系統提供一個簡單的接口
提供子系統的獨立性
在層次化結構中,可以使用外觀模式定義系統中每一層的入口。其中三層架構就是這樣的一個例子。
總結
到這里外觀模式的介紹就結束了,外觀模式,為子系統的一組接口提供一個統一的接口,該模式定義了一個高層接口,這一個高層接口使的子系統更加容易使用。并且外觀模式可以解決層結構分離、降低系統耦合度和為新舊系統交互提供接口功能。