C++設計模式之外觀模式(facade)(結構型)

一 引言

外觀模式(Facade)其實在開發過程中使用評率十分頻繁,或間接或直接使用,尤其是在當前各種第三方SDK 中,相當大的概率使用了外觀模式,通過一個外觀類使用的整個SDK的接口只有一個統一的高層接口,降低了用戶對接成本,也對用戶屏蔽了具體實現細節。

二 定義

外觀模式:外部與一個子系統的通信必須通過一個統一的外觀對象進行,爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。外觀模式又稱爲門面模式。

外觀模式核心思想在於“統一的對象”,即是提供一個訪問子系統的接口,除了這個接口不允許有任何訪問子系統的行爲發生(子系統是個概念,是所有類的簡稱,它可能代表一個類,也可能代表幾十個對象的集合),外觀對象是外界訪問子系統內部的唯一通道,不管子系統內部是多麼雜亂無章,所以外觀模式的核心參與者有:外觀對象和各種子系統角色。

關鍵詞:增加Facade層。

通過外觀的包裝,使應用程序只能看到外觀對象,而不會看到具體的細節對象,這樣無疑會降低應用程序的複雜度,並且提高了程序的可維護性。

三 模式分析

根據“單一職責原則”,在軟件中將一個系統劃分爲若干個子系統有利於降低整個系統的複雜性,一個常見的設計目標是使子系統間的通信和相互依賴關係達到最小,而達到該目標的途徑之一就是引入一個外觀對象,它爲子系統的訪問提供了一個簡單而單一的入口。

    外觀模式也是“迪米特法則”的體現,通過引入一個新的外觀類可以降低原有系統的複雜度,同時降低客戶類與子系統類的耦合度。

    外觀模式要求一個子系統的外部與其內部的通信通過一個統一的外觀對象進行,外觀類將客戶端與子系統的內部複雜性分隔開,使得客戶端只需要與外觀對象打交道,而不需要與子系統內部的很多對象打交道。

    外觀模式的目的在於降低系統的複雜程度。外觀模式從很大程度上提高了客戶端使用的便捷性,使得客戶端無須關心子系統的工作細節,通過外觀角色即可調用相關功能。

 

模式優點

1)對客戶屏蔽子系統組件,減少了客戶處理的對象數目並使得子系統使用起來更加容易。通過引入外觀模式,客戶代碼將變得很簡單,與之關聯的對象也很少。
2)實現了子系統與客戶之間的松耦合關係,這使得子系統的組件變化不會影響到調用它的客戶類,只需要調整外觀類即可。
3)降低了大型軟件系統中的編譯依賴性,並簡化了系統在不同平臺之間的移植過程,因爲編譯一個子系統一般不需要編譯所有其他的子系統。一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀對象。
4)只是提供了一個訪問子系統的統一入口,並不影響用戶直接使用子系統類。

模式缺點

1) 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
2) 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
外觀模式最大的缺點就是不符合開閉原則,對修改關閉,對擴展開放,看看我們那個門面對象吧,它可是重中之重,一旦在系統投產後發現有一個小錯誤,你怎麼解決?完全遵從開閉原則,根本沒辦法解決。繼承?覆寫?都頂不上用,唯一能做的一件事就是修改門面角色的代碼,這個風險相當大,這就需要大家在設計的時候慎之又慎,多思考幾遍纔會有好收穫。

適用性:

• 當要爲一個複雜子系統提供一個簡單接口時可以使用外觀模式。該接口可以滿足大多數用戶的需求,而且用戶也可以越過外觀類直接訪問子系統。

    • 客戶程序與多個子系統之間存在很大的依賴性。引入外觀類將子系統與客戶以及其他子系統解耦,可以提高子系統的獨立性和可移植性。

    • 在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯繫,而通過外觀類建立聯繫,降低層之間的耦合度。

注意

  • 子系統相對獨立——外界對子系統的訪問只要黑箱操作即可

  • 外觀對象不應參與各子系統之間的業務邏輯

四 實例

實例一:編譯器

舉個編譯器的例子,假設編譯一個程序需要經過四個步驟:詞法分析、語法分析、中間代碼生成、機器碼生成。學過編譯都知道,每一步都很複雜。對於編譯器這個系統,就可以使用外觀模式。可以定義一個高層接口,比如名爲Compiler的類,裏面有一個名爲Run的函數。客戶只需調用這個函數就可以編譯程序,至於Run函數內部的具體操作,客戶無需知道。下面給出UML圖,以編譯器爲實例。

#include <iostream>
#include <memory>
#include <utility>

class Scanner
{
public:
	void Scan() { std::cout << "詞法分析" << std::endl; }
};
class Parser
{
public:
	void Parse() { std::cout << "語法分析" << std::endl; }
};
class GenMidCode
{
public:
	void GenCode() { std::cout << "產生中間代碼" << std::endl; }
};
class GenMachineCode
{
public:
	void GenCode() { std::cout << "產生機器碼" << std::endl; }
};
//高層接口
class Compiler
{
public:
	Compiler()
	:scanner{ std::make_unique<Scanner>() }
	,parser{ std::make_unique<Parser>() }
	,genMidCode{ std::make_unique<GenMidCode>() }
	,genMacCode{ std::make_unique<GenMachineCode>() }
	{
	}
	void run()
	{
		scanner->Scan();
		parser->Parse();
		genMidCode->GenCode();
		genMacCode->GenCode();
	}

private:
	std::unique_ptr<Scanner> scanner;
	std::unique_ptr<Parser> parser;
	std::unique_ptr<GenMidCode> genMidCode;
	std::unique_ptr<GenMachineCode> genMacCode;
};

int main()
{
	Compiler compiler;
	compiler.run();

	return 0;
}

這就是外觀模式,它有幾個特點(摘自DP一書),(1)它對客戶屏蔽子系統組件,因而減少了客戶處理的對象的數目並使得子系統使用起來更加方便。(2)它實現了子系統與客戶之間的松耦合關係,而子系統內部的功能組件往往是緊耦合的。(3)如果應用需要,它並不限制它們使用子系統類。
       結合上面編譯器這個例子,進一步說明。對於(1),編譯器類對客戶屏蔽了子系統組件,客戶只需處理編譯器的對象就可以方便的使用子系統。對於(2),子系統的變化,不會影響到客戶的使用,體現了子系統與客戶的松耦合關係。對於(3),如果客戶希望使用詞法分析器,只需定義詞法分析的類對象即可,並不受到限制。

實例二:

#include <iostream>
#include <string>
#include <cstdlib>
#include <memory>
#include <utility>

//SubSystem Class,實現子系統的功能,處理Facade對象指派的任務。注意子類中沒有Facade任何信息,即沒有對Facade對象的引用。
class SubSystemOne
{
public:
	void MethodOne()
	{
		std::cout << "子系統方法一" << std::endl;
	}
};

class SubSystemTwo
{
public:
	void MethodTwo()
	{
		std::cout << "子系統方法二" << std::endl;
	}
};

class SubSystemThree
{
public:
	void MethodThree()
	{
		std::cout << "子系統方法三" << std::endl;
	}
};

class SubSystemFour
{
public:
	void MethodFour()
	{
		std::cout << "子系統方法四" << std::endl;
	}
};

//Facade Class,外觀類,知道有哪些子系統類,負責處理請求,將客戶的請求代理給適當的子系統對象。
class Facade
{
public:
	Facade()
		: one{std::make_unique<SubSystemOne>()}
		, two{ std::make_unique<SubSystemTwo>() }
		, three{ std::make_unique<SubSystemThree>() }
		, four{ std::make_unique<SubSystemFour>() }
	{
	}

	void MethodA()
	{
		std::cout << "方法組A()------" << std::endl;
		one->MethodOne();
		two->MethodTwo();
		four->MethodFour();
		std::cout << std::endl;
	}

	void MethodB()
	{
		std::cout << "方法組B()------" << std::endl;
		two->MethodTwo();
		three->MethodThree();
		std::cout << std::endl;
	}
private:
	std::unique_ptr<SubSystemOne> one;
	std::unique_ptr < SubSystemTwo> two;
	std::unique_ptr < SubSystemThree> three;
	std::unique_ptr < SubSystemFour> four;
};

//Client
int main()
{
	auto facade = std::make_unique<Facade>();

	facade->MethodA();
	facade->MethodB();

	return 0;
}

模式擴展

一個系統有多個外觀類

    • 在外觀模式中,通常只需要一個外觀類,並且此外觀類只有一個實例,換言之它是一個單例類。在很多情況下爲了節約系統資源,一般將外觀類設計爲單例類。當然這並不意味着在整個系統裏只能有一個外觀類,在一個系統中可以設計多個外觀類,每個外觀類都負責和一些特定的子系統交互,向用戶提供相應的業務功能。

不要試圖通過外觀類爲子系統增加新行爲

    • 不要通過繼承一個外觀類在子系統中加入新的行爲,這種做法是錯誤的。外觀模式的用意是爲子系統提供一個集中化和簡化的溝通渠道,而不是向子系統加入新的行爲,新的行爲的增加應該通過修改原有子系統類或增加新的子系統類來實現,不能通過外觀類來實現。

外觀模式與迪米特法則

    • 外觀模式創造出一個外觀對象,將客戶端所涉及的屬於一個子系統的協作夥伴的數量減到最少,使得客戶端與子系統內部的對象的相互作用被外觀對象所取代。外觀類充當了客戶類與子系統類之間的“第三者”,降低了客戶類與子系統類之間的耦合度,外觀模式就是實現代碼重構以便達到“迪米特法則”要求的一個強有力的武器。

抽象外觀類的引入

    • 外觀模式最大的缺點在於違背了“開閉原則”,當增加新的子系統或者移除子系統時需要修改外觀類,可以通過引入抽象外觀類在一定程度上解決該問題,客戶端針對抽象外觀類進行編程。對於新的業務需求,不修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置文件來達到不修改源代碼並更換外觀類的目的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章