C++设计模式 - 外观模式

161 阅读4分钟

胜,不妄喜;败,不遑馁;胸有激雷而面如平湖者,可拜上将军!                                                                  -- 孙子兵法

外观模式又叫门面模式,是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。

**「外观模式」**的作用主要是封装一套一致对外的接口,具体实现由多个独立模块函数协作完成。如此一来,客户端需要实现一个复杂的功能时,无需关注具体的实现,只需要调用一个接口即可。

意义

**「外观模式」**非常适用于解决客户端业务逻辑代码与第三方类代码的耦合问题。
图片Android源码Hal接口虽然是C语言实现,但是其应用到了外观模式的设计思想。Hal层运用结构体指针封装了一套对外的功能接口,其内部硬件操作由各个驱动实现。
当应用层控制某一个硬件时,只需要调用对应的Hal接口即可,无需关注具体实现。如此一来,驱动与应用通过Hal层便实现了解耦。

实际场景

计划一次旅行时,并规划出此次旅行的路线和所需要携带的装备。

分析

旅行路线取决于出发地与目的地,携带装备取决于旅行的天气与时间。因此需要通过地图APP挑选出最佳路线,通过天气APP了解旅行期间两地天气温度,从而准备好装备。图片

上述方法对于用户很不友好,需要分别查阅地图APP与天气APP,从而得出具体的计划。若存在一个APP能够访问地图APP、和天气APP获取的信息,用户便可通过一个APP实现相同的效果。图片

类图

图片

外观模式

  • CPlan: 提供客户端使用的接口。

  • CWeatherManager: 可访问天气的功能类。

  • CRouteManager: 可访问地图及交通的功能类。

  • CEquipment: 结合天气得出携带装备的功能类。

源码实现

「编程环境」

  1. 编译环境: Linux环境

  2. 语言: C++语言

  3. 编译命令: make

「工程结构」

Facade/
├── equipment_manager.cc
├── equipment_manager.h
├── main.cc
├── Makefile
├── plan.cc
├── plan.h
├── route_manager.cc
├── route_manager.h
├── whether_manager.cc
└── whether_manager.h

  • plan: 对客户端main.c提供统一接口。

  • equipment_manager,route_manager,whether_manager: 具体获取装备、地图和天气的功能类。

  • Makefile: 编译工具

  • main: 客户端代码

「对客户端的接口」

// plan.h
class CPlan
{
public:
    CPlan() {}

    ~CPlan() {}

    int TravelPlan(int startDay, int endDay, std::string place);

    void SetCurPlace(std::string place) { mCurPlace = place; }

    std::string GetCurPlace() { return mCurPlace; }

private:
    std::string mCurPlace;
};

「对外接口的实现」

// plan.cc
int CPlan::TravelPlan(int startDay, int endDay, std::string place)
{
    CWhetherManager   *theWhetherManager   = CWhetherManager::GetInstance();
    CRouteManager     *theRouteManager     = CRouteManager::GetInstance();
    CEquipmentManager *theEquipmentManager = CEquipmentManager::GetInstance();

    if (mCurPlace.empty())
    {
        PLAN_LOGE("Current place is empty\n");
        return -1;
    }

    PLAN_LOG("---------------  Travel Plan  --------------- \n"
             "- Place : %s -> %s                 \n"
             "- Time  : %s -> %s                 \n"
             "- Wheher: %s(%s)                   \n"
             "- Route : %s                       \n"
             "- Prepare Equipment: %s            \n",
             GetCurPlace().c_str(), place.c_str(),
             std::__cxx11::to_string(startDay).c_str(), std::__cxx11::to_string(endDay).c_str(),
             theWhetherManager->GetDuringWeather(startDay, endDay, place).c_str(), place.c_str(),
             theRouteManager->GetRoutePlan(mCurPlace, place).c_str(),
             theEquipmentManager->GetEquipment(startDay, endDay, place).c_str()
    );
    return 0;
}

  • 在外观接口的实现中,调用了第三方类CWhetherManager、CRouteManager、CEquipmentManager。第三方类的具体业务本例程没有打算实现。

  • 具体原理就是在对外接口的实现上,调用一个或多个第三方类满足外观接口的功能。

「第三方类代码」
route_manager.h

//route_manager.h
class CRouteManager
{
public:
    CRouteManager() {}

    ~CRouteManager() {}

    static CRouteManager* GetInstance();

    std::string GetRoutePlan(std::string curPlace, std::string dstPlace);
};

route_manager.cc

// route_manager.cc
CRouteManager* CRouteManager::GetInstance()
{
    static CRouteManager mObj;
    return &mObj;
}

string CRouteManager::GetRoutePlan(std::string curPlace, std::string dstPlace)
{
    string routePlan = "xxxx";

    return routePlan;
}

  • 为避免影响查阅,这里仅route_manager的声明和实现。完整工程在后台输入标题获取。

「客户端代码」

//main.cc
int main(int argc, char *argv[])
{
    CPlan thePlan;

    thePlan.SetCurPlace("ShenZhen-BaoAn");
    thePlan.TravelPlan(2022100120221003"BeiJing-TianAnMen");

    return 0;
}

测试效果

$ ./exe 
---------------  Travel Plan  --------------- 
- Place : ShenZhen-BaoAn -> BeiJing-TianAnMen                 
- Time  : 20221001 -> 20221003                 
- Wheher: xxxx(BeiJing-TianAnMen)                   
- Route : xxxx                       
- Prepare Equipment: xxxx     

  • 由于第三方类的业务没有实现,这里就没有具体的计划。能够看出代码已经执行的第三方类中即可。

总结

  • **「外观模式」**实现起来比较简单,其存在的意义大概有:一是将复杂的调用简易化。二是将业务代码和功能代码解耦,实现层级关系。三是能够使功能代码更容易的复用。

  • 本例只是一个简单的demo测试,主要在于理解**「外观模式」的思想。在实际编程中,Android Hal层的存在就是外观模式的一种应用。除此之外,每个调用层级之间的衔接,很多都是「外观模式」**的体现。

  • **「外观模式」一般可以「单例模式」**存在,大部分情况下一个外观对象足以满足需求。

  • **「外观模式」**看上去比较简单,其实用性是比较大的。在日常的编码中运用此模式,能够使代码逻辑更加清晰,“墙裂”推荐!

往期回顾

C++设计模式 - 中介者模式

Linux进程间通信 消息队列

Linux字符驱动模板实现

所有文章目录

本文使用 文章同步助手 同步