目录
一.专栏简介
本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。
本章将开始外观模式的学习。
二.Jsoncpp库为例子
Jsoncpp 是一个纯 C++ 编写的开源库,无任何外部依赖,核心作用是:
- 解析(反序列化):将 JSON 格式的字符串 / 文件,解析成 C++ 中可直接操作的对象;
- 生成(序列化):将 C++ 中的 Jsoncpp 对象,拼接 / 转换为标准的 JSON 格式字符串,也可直接写入文件;
- 支持完整的 JSON 语法:键值对、数组、嵌套结构、空值 (null)、各种基础数据类型 (int/double/bool/string)。
它的Json命名空间里的StreamWriterBuilder类 和CharReaderBuilder类 是一个简单工厂,用来创建Json命名空间里的StreamWriter 和CharReader ,它们就可以分别调用write 和parse方法来对Json::Value序列化为string字符串和string反序列化为Json::Value对象。
我们不希望我们在使用的时候每次都用这个简单工厂创建一个对象,然后才序列化或者反序列化,这造成代码的重复,麻烦,难以维护。
那么我们就封装了以下这个工具类:
cpp
class json_util
{
public:
static bool serialize(const Json::Value& root, std::string& str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
int ret = sw->write(root, &ss);
if(ret != 0)
{
ELOG("Json序列化失败");
return false;
}
str = ss.str();
return true;
}
static bool unserialize(const std::string& str, Json::Value& root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
if(!cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err))
{
ELOG("Json反序列化失败,原因:%s" , err.c_str());
return false;
}
return true;
}
};
两个工具函数,用于反序列化和反序列化。正如函数参数,我们只关心一个Json::Value可以序列化为一个string或者一个string可以反序列化为一个Json::Value对象,其它的东西我们不关心,所以我们把里面的细节都封装了起来。
三.外观模式
OK!上面的封装就是外观模式的应用。
外观模式为子系统中的一组接口提供了一个统一的接口。外观定义了一个更高级别的接口,使得子系统容易被使用。
外观提供一个简化的接口,但依然暴露系统的全部功能给需要的客户。
外观不仅简化接口,它还把客户从组件的子系统解耦。
外观和适配器的区别:外观和适配器可以包装多个类,但外观的意图是简化,而适配器的意图是转化接口为不同的接口。

下面我们由外观模式引出一个原则:最少知识原则。
四.最少知识原则
最少知识原则引导我们减少对象之间的交互,减少到仅发生在一些亲密"朋友"之间。这个原则通常表述如下:

但这到底是什么意思?意思是,当我们设计一个系统时,对于任何对象,都要注意它所交互的类的数量,以及它和这些类如何交互。
这个原则防止我们创建有大量的类在一起的设计,免得系统一部分的变化会连锁影响到其他部分。当你在许多类之间造成许多依赖时,我们的系统就是一个易碎的系统,需要花费许多成本维护,而且复杂得让别人难以理解。
五.如何不赢得朋友和影响对象
该原则提供了一些指南:对于任何对象,从该对象的任何方法,只调用属于以下范围的方法:
- 对象自身。
- 作为参数传给方法的对象。
- 该方法创建或实例化的任何对象。
- 对象的任何组件。(has-a)
这些指南告诉我们,不要调用从其他方法返回的对象的方法!
调用从另一个调用中返回的对象的方法,有什么坏处呢?好吧,如果我们这样做,相当于向另一个对象的子部分发出请求(而增加我们直接认识的对象数目)。在这种情况下,原则告诉我们,要求该对象为我们做出请求,这样我们就不必知道它的组件对象(保持我们的朋友圈尽可能小)。例如:
不遵守原则:
cpp
float getTemp()
{
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
这里,我们从气象站取得温度计(thermometer)对象,然后自行调用getTemperature()方法。
遵守原则:
cpp
float getTemp()
{
return station.getTemperature();
}
当我们应用该原则时,我们给Station(气象站)类添加一个方法 ,向温度计发出请求。这减少了所依赖的类的数目。
六.遵守最少知识原则的类
这是一个Car(汽车)类,展示了我们在调用方法同时依然遵守最少知识原则的所有方式:
cpp
class Car
{
public:
Car()
{
}
// 我们可以调用作为参数传递的对象的方法
void start(Key key)
{
// 这里我们创建了一个新的对象:调用它的方法是合法的
Doors* doors = new Doors();
// 我们可以调用作为参数传递的对象的方法
bool authorized = key.turns();
if(authorized)
{
// 我们可以调用对象组件的方法
engine.start();
// 我们可以调用对象内的局部方法
updateDashboardDisplay();
// 我们可以调用我们创建或实例化的对象的方法
doors.lock();
}
}
void updateDashboardDisplay()
{
// 更新显示
}
private:
// 这是这个类的一个组件,我们可以调用它的方法
Engine engine;
};
七.外观模式和最少知识原则

八.总结
原则不叫法律是有原因的,我们不是必须遵循,而是看实际情况,具体问题具体分析。
虽然最少知识原则减少了对象之间的依赖,研究显示这会减少软件维护成本,应用这个原则也会导致编写更多的"包装者"类来处理对其他组件的方法调用。这会造成复杂度和开发时间增加,运行时性能下降。
下面是一些要点,包含本专栏上一篇博客适配器的:
- 当你需要使用一个已有的类,而其接口不符合你的需要,就用适配器。
- 当你需要简化并统一一个大接口,或者一个复杂的接口集,就用外观。
- 适配器改变接口以符合客户的期望。
- 外观将客户从一个复杂子系统解耦。
- 实现一个外观,需要把外观和子系统结合,使用委托来执行外观的工作。
- 适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承。
- 我们可以为一个子系统实现多于一个外观。
- 适配器包装一个对象以改变其接口,装饰者包装一个对象以添加新的行为和责任,而外观"包装"一群对象以简化其接口。