外观模式

其实这个模式在日常使用过程中被使用到的频率特别高,只是不清楚原来这种这么简单的手法还有一个挺高大上的名字 —— 外观模式,又叫 门面模式

意图

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

动机

我很喜欢 《大话设计模式》 引出这种设计模式时用到的例子,基金和股票的关系

众所周知,股票 是一种相对而言风险比较高的理财方式,一支股票由一支公司发行的,当我们持有多家的优质公司发行的股票的时候,因为涉及到对自己投资资金的规划,随着市场的波动,我们可能需要动态的调整持仓的占比等等,因此我们的操作的复杂度也上来了,可这对于大多数理财小白而言上手难度有点大

而什么是 基金 呢?简单点说就是安排个基金经理,他用资金去投资他认为优秀的股票,债券等等领域,由他来全权负责。这意味如果我们买了基金,实际上我们是不用操作股票,只有一个简单的买卖接口

所以对于我而言,直接选择一支优秀的基金,然后交给基金经理在后台对持仓进行复杂的调整

关于理财感兴趣的可以看一下 B 站 UP 主 这是Morty 的科普视频 https://www.bilibili.com/video/BV16z4y167tz

扯远了,回来 外观模式,这里的基金其实就是股票的一个 门面,我们可以很简单的操作基金提供的 高层接口, 不用操作复杂的子系统股票,当然如果需要定制化操作时,用户也可以越过基金 (门面) 这一层

结构图

外观模式其实没有所谓的 UML 图, 更多是对复杂子系统调用的一种结构调整

uml

图中,客户端本来是操作 对象A对象C 的,使用外观模式将整个子系统包装一下,让客户端通过 Facade 来操作,此时的 Facade 是可以提供一组更为简洁的接口

  • 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
  • 降低了客户-子系统之间的耦合度
    用抽象类实现 Facade 而它的具体子类对应于不同的子系统的实现,这样可以进一步降低客户和子系统的耦合度

使用场景

  • 当你要为一个复杂的子系统提供一个简单的接口时。
    子系统往往因为不断的演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类,这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。外观模式可以提供一个简单的缺省实现,而这个对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过 Facade
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性,引入 Facade 将这个子系统与客户端以及其他的子系统分离,可以提高子系统的独立性和可移植性
  • 当你需要构建一个层次结构的子系统时,使用外观模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让他们仅通过 Facade 进行通信,从而简化了他们之间的依赖关系

来个例子

直接前面提到的股票和基金的例子:

先定义2支股票,分别是茅台和特斯拉:

#include <iostream>
using namespace std;

class MoutaiStock
{
public:
    void buy(int money) {cout << "buy MoutaiStock: " << money << endl;}
};

class TeslaStock
{
public:
    void buy(int money) {cout << "buy TeslaStock: "<< money  << endl;}
};

定一支基金,主要就是持有这2支股票,在买入基金的时候,7成买茅台股票,3成买特斯拉股票:

#include "shares.h"
#define SAFE_DELETE(p) if(p) {delete p; p=nullptr;}

class EFund{
public:
    EFund() : m_pMoutaiStock(new MoutaiStock), m_pTeslaStock(new TeslaStock) {}
    ~EFund() { SAFE_DELETE(m_pMoutaiStock); SAFE_DELETE(m_pTeslaStock); }

    void buy(int money){
        cout << "buy EFund: " << money << endl;
        m_pMoutaiStock->buy(money/10*7);
        m_pTeslaStock->buy(money/10*3);
    }

private:
    MoutaiStock *m_pMoutaiStock;
    TeslaStock *m_pTeslaStock;
};

最后客户端买基金的代码就很简单了

#include "fund.h"

int main()
{
    EFund e_fund;
    e_fund.buy(100);

    return 0;
}

运行结果

buy EFund: 100
buy MoutaiStock: 70
buy TeslaStock: 30

总结

外观模式在我眼里更多是个重新封装的过程,将一些复杂模块的代码进行封装从而对客户隐藏具体细节,并且提供给客户更简洁的接口~