第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)外观模式与中介者模式

  ①这两者很相似,但有本质的区别

  ②中介者用来封装多个对象之间的交互,多用在系统内部的多个模块之间;而外观的封装是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用。

  ③在中介者模式的实现里面,需要实现具体的交互功能;而外观模式的实现里面,一般是组合调用或转调内部实现的功能,通常外观模式本身并不实现这些功能。

  ④中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系全部放到中介者中去实现;而外观模式的目的是简化客户端的调用。

猜你喜欢

转载自blog.csdn.net/CherishPrecious/article/details/83987133