胜,不妄喜;败,不遑馁;胸有激雷而面如平湖者,可拜上将军! -- 孙子兵法
❝
外观模式又叫门面模式,是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。
❞
**「外观模式」**的作用主要是封装一套一致对外的接口,具体实现由多个独立模块函数协作完成。如此一来,客户端需要实现一个复杂的功能时,无需关注具体的实现,只需要调用一个接口即可。
意义
**「外观模式」**非常适用于解决客户端业务逻辑代码与第三方类代码的耦合问题。
Android源码Hal接口虽然是C语言实现,但是其应用到了外观模式的设计思想。Hal层运用结构体指针封装了一套对外的功能接口,其内部硬件操作由各个驱动实现。
当应用层控制某一个硬件时,只需要调用对应的Hal接口即可,无需关注具体实现。如此一来,驱动与应用通过Hal层便实现了解耦。
实际场景
计划一次旅行时,并规划出此次旅行的路线和所需要携带的装备。
分析
旅行路线取决于出发地与目的地,携带装备取决于旅行的天气与时间。因此需要通过地图APP挑选出最佳路线,通过天气APP了解旅行期间两地天气温度,从而准备好装备。
上述方法对于用户很不友好,需要分别查阅地图APP与天气APP,从而得出具体的计划。若存在一个APP能够访问地图APP、和天气APP获取的信息,用户便可通过一个APP实现相同的效果。
类图
外观模式
-
CPlan: 提供客户端使用的接口。
-
CWeatherManager: 可访问天气的功能类。
-
CRouteManager: 可访问地图及交通的功能类。
-
CEquipment: 结合天气得出携带装备的功能类。
源码实现
「编程环境」
-
编译环境: Linux环境
-
语言: C++语言
-
编译命令: 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(20221001, 20221003, "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层的存在就是外观模式的一种应用。除此之外,每个调用层级之间的衔接,很多都是「外观模式」**的体现。
-
**「外观模式」一般可以「单例模式」**存在,大部分情况下一个外观对象足以满足需求。
-
**「外观模式」**看上去比较简单,其实用性是比较大的。在日常的编码中运用此模式,能够使代码逻辑更加清晰,“墙裂”推荐!
往期回顾
本文使用 文章同步助手 同步