C++设计模式之外观模式(facade)(结构型)

故事扮演 提交于 2020-01-24 01:48:53

一 引言

外观模式(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;
}

模式扩展

一个系统有多个外观类

    • 在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。

不要试图通过外观类为子系统增加新行为

    • 不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。

外观模式与迪米特法则

    • 外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。

抽象外观类的引入

    • 外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!