博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【设计模式】空对象设计模式学习
阅读量:5798 次
发布时间:2019-06-18

本文共 2404 字,大约阅读时间需要 8 分钟。

解决问题

之前看设计模式的书并没有看到过Null Object设计模式, 所谓空对象设计模式,实际上是为了规避客户端获取一个对象后(比如是指针对象),在后面调用的所有地方都要判空,否则调用方法(或者解引用)那可能就有问题了,轻则coredump重则程序没有挂但是运行是不对的. 下面针对一个比较简单的例子给出场景:

std::shared_ptr
n;int x = *n + 1;

因为n本身没有被初始化,对其接引就会当掉了, 对于shared_ptr<>解引用实际上就是对存储的指针解引用:

解引用所存储的指针。若存储的指针为空,则行为未定义.

那么对于服务端提供的类对象来说,道理是一样的

ServerClass  *obj;...obj->dosomething();

这时候,客户端程序员就很没有安全感了, 不知道对于空对象执行成员函数结果是什么样的. 为了解决此问题,客户端不得不在所有用到的地方判空. 麻烦且容易出错. 这时候空对象设计模式就派上用场了.

原理

先明确要解决的问题,上文中给出了客户端不得不应对处理这些空指针(空对象), 如果这个脏活能拿到服务端类对象里面就好了,毕竟客户端多次调用,但是服务端的类(或者API)只写一次就好了. 所以服务端要做的有两个事情:

  1. 在类对象构造的时候,区分空对象和可用对象
  2. 在客户端调用类成员函数的时候,根据对象是否为空,给出不同的行为.

按以上两种处理即可. 实现的方式也有两种,列出如下,实现中分别给出阐述.

  1. 新定义定义一个空对象类,所有成员函数设置为空(或者定制化)
  2. 底层使用std::optional包装真正对象,而std::optional天然可以区分对象是否空对象,未初始化状态就是空对象.

实现

场景:假定服务端提供了日志记录的接口,客户端使用日志接口中的info()功能,客户端多处使用,如果客户端使用空对象,预期的行为是啥也不干,日志类接口如下:

struct Logger{    virtual ~Logger() = default;    virtual void info(const std::string &s) =0;};

按照原理所述,给用户提供的接口类应该能够包装空对象和实际对象. 调用的时候对对象的存在性进行判断而左右行为. 因此需要新增空对象并对其包装.相关处理如下:

// 新增空对象类struct NullLogger : Logger{    void info(const std::string &s) override {    }};// 对客户端提供接口,内含设计类`impl`和空对象`no_logging`// 可以看出如果实际类对象不存在则调用了这个成员函数`info`也不会有实际行为,客户端程序变安全了.struct OptionalLogger : Logger{    std::shared_ptr
impl; static std::shared_ptr
no_logging; OptionalLogger(const std::shared_ptr
&logger) : impl(logger) {} void info(const std::string &s) override { if(impl) impl->info(s); }};

继续讲第二种方式(c++17std::optional实现),因为本地没有c++17编译器,因此用boost::optional来代替. optional天然就能包装有值的类和未初始化的空对象, 因此不需要额外定义,相对更简单,实现如下:

struct OptionalLogger2 : Logger{    boost::optional
> impl; OptionalLogger2(const std::shared_ptr
&logger) { // 对于对象impl的初始化工作可以自行定义. `shared_ptr<>`可以直接和`nullptr`进行比较 // if(nullptr != logger) impl = logger; } // 直接判断是否是空对象 void info(const std::string &s) override { if(impl) (*impl)->info(s); }};

这样客户端调用的时候就方便多了,判断对象合法性基本不用管,如果调用的成员方法不是必要,那可以安排空对象. 客户端调用方式:

auto log2 = std::make_shared
(nullptr);...// 安全调用log2->info("");

总结

与其说这是一个设计模式, 更不如说这是API设计的一个最佳实践,特别是在std::optional推出之后,就算不考虑面向对象,在函数返回值的策略上也可以变得很易用,不用再用千奇百怪的负值作为非法返回值(-1 -999)了,因为可以用一个std::optional<int>同时包装调用成功失败的状态和查询到的返回值,这样更加优美了; 回到这个主题,std::optional的出现,最佳实践更加简单,大量减轻客户端负担,优点如下:

  • 不需要认为的对对象的合法性进行判断,就可以保证运行时安全. 不依赖client端.
  • 对于调用空对象方法的结果, server端控制.

参考

转载地址:http://rpsfx.baihongyu.com/

你可能感兴趣的文章
项目管理心得
查看>>
Android自学--一篇文章基本掌握所有的常用View组件
查看>>
排序算法总结(一)插入排序【Insertion Sort】
查看>>
Java SE、Java EE、Java ME 三者区别
查看>>
灰度图像和彩色图像
查看>>
通过vb.net 和NPOI实现对excel的读操作
查看>>
TCP segmentation offload
查看>>
你必须知道的28个HTML5特征、窍门和技术【转】
查看>>
java数据类型
查看>>
360导航板式
查看>>
闪回恢复区大小不够。报ORA-19809、ORA-19804
查看>>
C#类的构造
查看>>
部署 & virtualen
查看>>
POJ NOI0105-43 质因数分解
查看>>
数据结构——串的朴素模式和KMP匹配算法
查看>>
jQuery的height()和JavaScript的height总结,js获取屏幕高度
查看>>
FreeMarker-Built-ins for strings
查看>>
验证DataGridView控件的数据输入
查看>>
POJ1033
查看>>
argparse - 命令行选项与参数解析(转)
查看>>