设计模式:外观模式(Facade)
- 设计模式:外观模式(Facade)
- 模式动机
- 模式定义
- 模式结构
- 时序图
- 模式实现
- 在单线程环境下的测试
- 在多线程环境下的测试
- 模式分析
- 优缺点
- 适用场景
- 应用场景
- 模式扩展
- 参考
设计模式:外观模式(Facade)
外观模式(Facade)属于结构型模式(Structural Pattern)的一种。
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
结构型模式可以分为类结构型模式和对象结构型模式:
- 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
- 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
模式动机
隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。
模式定义
外观模式(Facade)又称为门面模式,属于结构型模式。
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
模式结构
迭代器模式(Iterator)包含如下角色:
- 外观(Facade):提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。
- 子系统(Subsystem):由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。但是,SubSystem没有Facade的任何相关信息,也就是说,没有指向Facade的指针。
- 客户端(Client):使用外观对象来与系统交互,而不需要了解系统内部的具体实现。
子系统(Subsystem)之间也可能存在关联关系、继承关系等关系,这里为了简化没有画出来。
时序图
模式实现
外观 Facade.h:
#ifndef _FACADE_H_
#define _FACADE_H_
#include "SystemA.h"
#include "SystemB.h"
#include "SystemC.h"
class Facade
{
private:
SystemA* m_SystemA;
SystemB* m_SystemB;
SystemC* m_SystemC;
public:
Facade()
{
m_SystemA = new SystemA();
m_SystemB = new SystemB();
m_SystemC = new SystemC();
}
~Facade()
{
delete m_SystemA;
delete m_SystemB;
delete m_SystemC;
}
void wrapOpration()
{
m_SystemA->operationA();
m_SystemB->operationB();
m_SystemC->operationC();
}
};
#endif // !_FACADE_H_
三个子系统:
#ifndef _SYSTEM_A_H_
#define _SYSTEM_A_H_
#include <iostream>
class SystemA
{
public:
void operationA()
{
std::cout << "operationA" << std::endl;
}
};
#endif // !_SYSTEM_A_H_
#ifndef _SYSTEM_B_H_
#define _SYSTEM_B_H_
#include <iostream>
class SystemB
{
public:
void operationB()
{
std::cout << "operationB" << std::endl;
}
};
#endif // !_SYSTEM_B_H_
#ifndef _SYSTEM_C_H_
#define _SYSTEM_C_H_
#include <iostream>
class SystemC
{
public:
void operationC()
{
std::cout << "operationC" << std::endl;
}
};
#endif // !_SYSTEM_C_H_
在单线程环境下的测试
测试代码,也可以说是 client:
#include <stdlib.h>
#include "Facade.h"
int main()
{
Facade* facade = new Facade();
facade->wrapOpration();
system("pause");
return 0;
}
运行结果:
在多线程环境下的测试
略。
模式分析
- 根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
- 外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
- 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
- 外观模式的目的在于降低系统的复杂程度。
- 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。
优缺点
优点:
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
缺点:
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
适用场景
- 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。
- 客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
应用场景
- 我们在使用visual studio进行编译C++代码时,你只是在菜单中选择了Build,然后visual studio就开始了一堆的编译工作;你应该知道,因为你的一个简单的Build动作,编译器在后台会进行语法分析,生成中间代码,生成汇编代码,链接成可执行程序或库等等动作。Build在幕后,将任务分发给不同的子系统去完成,最终子系统进行协作完成了整个的编译任务。
- Java的三层开发模式:在数据访问层,与业务逻辑层表示层之间,建立Facade。
模式扩展
- 一个系统有多个外观类
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。 - 抽象外观类的引入
外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
参考
- https://design-patterns.readthedocs.io/zh-cn/latest/structural_patterns/facade.html
- https://www.runoob.com/design-pattern/facade-pattern.html
- https://blog.csdn.net/weixin_45433817/article/details/131037102
- https://www.cnblogs.com/ring1992/p/9593112.html