第12章 结构型模式—外观模式
1. 外观(门面)模式(Facade Pattern)的定义
(1)为子系统中的一组接口提供一个一致的界面,Façade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
①这里说的界面是指从一个组件外部来看这个组件,能够看到什么,也就是外观。如从一个类外部看这个类,那么这个类的public方法或接口就是他的外观。
②这里所说的“接口”是指外部和内部交互的一个通道(方法),可以是类的方法,也可以是interface的方法。
(2)外观模式的结构和说明
①Façade:定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象
②模块:接受Façade对象的委派,真正实现功能,各个模块之间可能有交互。但请注意,Façade对象知道各个模块,但是各个模块不应该知道Façade对象。
(3)思考外观模式
①外观模式的本质:封装交互、简化调用。Façade封装了子系统外部和子系统内部多个模块的交互过程,从而简化了外部的调用,通过外观,子系统为外部提供一些高层的接口,以方便它们的使用。它很好的体现了“最少知识原则(DoL)”
②外观模式的目的:外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内部多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。
③Façade表面上看只是把客户端的代搬到了Façade里,但是有本质上的区别。因为Façade位于系统而不是客户端的一侧,它相当于屏蔽了外部客户端和系统内部模块的交互,同时Façade可以被多个客户端调用,实现复用。
④外观是当作子系统对外的接口出现的,虽然可以在这里定义一些子系统没有的功能,但不建议这样做。外观应该是包装己有的功能,而不是添加新的功能。因为有时客户端可能需要绕开Façade,直接调用某个具体模块。而如果在Façade中添加新的功能,但可能最后调用的结果是不同的。
⑤外观可以提供一些缺省的功能实现。
【编程实验】注册公司(通过外观模式提供简化的办理流程)
//结构型模式:外观模式
//场景:注册公司。
#include <iostream>
using namespace std;
//质检局:Quality Supervision Burean
class IQSBureau
{
public:
//到质检局办理组织机构代码证
virtual void orgCodeCertificate() = 0;
};
class QSBureau : public IQSBureau
{
public:
void orgCodeCertificate()
{
cout <<"QSBureau: orgCodeCertificate()" << endl;
}
};
//工商局:Industrial and Commercial Bureau
class IICBureau
{
public:
//检查名字是否冲突
virtual void checkName() = 0;
};
class ICBureau : public IICBureau
{
public:
void checkName()
{
cout << "ICBureau: checkName()" << endl;
}
};
//税务局: Tax Bureau;
class ITaxBureau
{
public:
//办理税务登记证
virtual void taxCertificate() = 0;
};
class TaxBureau: public ITaxBureau
{
public:
void taxCertificate()
{
cout << "TaxBureau: taxCertificate()" << endl;
}
};
//银行:Bank;
class IBank
{
public:
//到银行开户
virtual void openAccout() = 0;
};
class Bank : public IBank
{
public:
void openAccout()
{
cout <<"Bank: openAcount()" << endl;
}
};
//提供一个外观类(注册公司)
class RegisterFacade
{
private:
IICBureau* ic;
IQSBureau* qs;
IBank* bank;
ITaxBureau* tb;
public:
RegisterFacade()
{
ic = new ICBureau();
qs = new QSBureau();
bank = new Bank();
tb = new TaxBureau();
}
void registerCompany()
{
ic->checkName(); //工商局
qs->orgCodeCertificate(); //质检局
tb->taxCertificate(); //税务局
bank->openAccout(); //银行开户
}
~RegisterFacade()
{
delete ic;
delete qs;
delete bank;
delete tb;
}
};
int main()
{
//客户端调用
RegisterFacade rf;
rf.registerCompany();
return 0;
}
2. 外观模式的实现
(1)Façade的实现
对于一个子系统而言,外观类不需要很多,通常可以实现成一个单例。也可以直接把外观中的方法实现成静态方法,这样就可以不需要创建外观对象就可以直接调用,这种实现相当于把外观类当成一个辅助的工具类实现。
(2)Façade可以实现为Interface
通常Façade直接实现为为,但是也可以实现成真正的接口,这里还需要一个Façade的实现,一般可以用工厂方法来获取实现Façade接口的对象。实现为接口的好处是能够有选择地暴露接口,尽量减少模块对子系统对外提供的接口方法。
(3)Façade的方法
一般是负责把客户端的请求转发给子系统内部的各个模块进行处理, Façade的方法实现的只是一个功能的组合调用和转发。但Façade不应参与子系统内的业务逻辑,这会导致子系统必须依赖外观才能被访问,这超出了Façade的本意,也违反了单一职责原则。
3. 外观模式的优缺点
(1)优点
①松散耦合:外观模式松散了客户端与子系统的耦合,让子系统内部的模块能更容易扩展和维护。
②简单易用:客户端不再需要了解子系统内部实现,也不需要与众多的子系统内部模块进行交互,只需要跟外观交互就可以了。相当于外部类为客户端使用子系统提供了一站式服务。
③更好地划分访问的层次。有些方法是对系统外的,有些是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节。
(2)缺点:不符合开闭原则,有时可能需要修改外观角色的代码;过多使用Façade容易让人迷惑。
4. 外观模式的使用场景
(1)为一个复杂的模块或子系统提供一个外界访问的接口
(2)子系统相对独立,外界对子系统访问只要黑箱操作即可。如利息计算很复杂,需要深厚的业务知识和扎实的技术水平。但对于客户端来讲,要求输入金额和存期,就可以返回最终的利息。这里可以用外观模式。
(3)当需要构建一个层次结构的子系统时,使用Façade定义子系统中每层的入口点。如果子系统是相互依赖的,可以让它们仅通过Façade进行通讯,从而简化它们的依赖关系。
【编程实验】编译过程的模拟
//结构型模式:外观模式
//场景:编译过程的简化。
//编译器内部所做的事情:词法分析、语法分析、代码优化、代码生成等
//对于客户端来讲,只要给文件路径,只能产生最终的结果。编译器内部所做的
//事情对客户端来说是透明的。
#include <iostream>
using namespace std;
//扫描器
class Scanner
{
public:
void scan(const char* lpszInput)
{
cout <<"Scanning: " << lpszInput << endl;
}
};
//分析器
class Parser
{
public:
void parse(const char* lpszInput)
{
cout <<"Analysing: " << lpszInput << endl;
}
};
//代码生成器
class CodeGenerator
{
public:
void codeGen(const char* lpszOutput)
{
cout <<"Code generate, output to: " << lpszOutput<< endl;
}
};
//提供一个外观类(编译器)
class Compiler
{
private:
Scanner s;
Parser p;
CodeGenerator c;
public:
void compile(const char* lpszInput, const char* lpszOutput)
{
s.scan(lpszInput);
p.parse(lpszInput);
c.codeGen(lpszOutput);
}
};
//使用编译系统:这个系统包括扫描器、分析器、生成器。并且提供了一个外观接口
//Compiler类
int main()
{
//客户端调用
Compiler c;
c.compile("123.cpp", "123.exe");
return 0;
}
5. 相关模式
(1)外观模式与中介者模式
①这两者很相似,但有本质的区别
②中介者用来封装多个对象之间的交互,多用在系统内部的多个模块之间;而外观的封装是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用。
③在中介者模式的实现里面,需要实现具体的交互功能;而外观模式的实现里面,一般是组合调用或转调内部实现的功能,通常外观模式本身并不实现这些功能。
④中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系全部放到中介者中去实现;而外观模式的目的是简化客户端的调用。