實例引入
在家庭影院中,有燈光,屏幕,投影機,功放機,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 106using
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 57namespace
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 26using
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)角色:可以同時包含一個或多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合。每個子系統都可以被客戶端直接調用或被門面角色調用。對於子系統而言,門面僅僅是另外一個客戶端,子系統並不知道門面的存在。
外觀的優缺點
優點:
外觀模式對客戶屏蔽了子系統組件,從而簡化了接口,減少了客戶處理的對象數目並使子系統的使用更加簡單。
外觀模式實現了子系統與客戶之間的松耦合關系,而子系統內部的功能組件是緊耦合的。松耦合使得子系統的組件變化不會影響到它的客戶。
缺點:
如果增加新的子系統可能需要修改外觀類或客戶端的源代碼,這樣就違背了”開——閉原則“(不過這點也是不可避免)。
使用場景
在以下情況下可以考慮使用外觀模式:
外一個復雜的子系統提供一個簡單的接口
提供子系統的獨立性
在層次化結構中,可以使用外觀模式定義系統中每一層的入口。其中三層架構就是這樣的一個例子。
總結
到這裡外觀模式的介紹就結束了,外觀模式,為子系統的一組接口提供一個統一的接口,該模式定義了一個高層接口,這一個高層接口使的子系統更加容易使用。並且外觀模式可以解決層結構分離、降低系統耦合度和為新舊系統交互提供接口功能。