本文是本书最后一篇文章,完结撒花!谢谢大家观看!

目录:

trick:Hands-On Design Patterns With C++(零)前言​zhuanlan.zhihu.com

访问者模式与多分派(下)

本文概要:

(1)编译期的访问者模式

(2)访问者模式与组合模式融合

PS:本文代码地址:https://github.com/PacktPublishing/Hands-On-Design-Patterns-with-CPP/tree/master/Chapter18

编译期访问者模式

本节我们讨论在编译时使用访问者模式的可能性。首先,模板使访问者模式实现多重调度很方便:

template <typename T1, typename T2> auto f(T1 t1, T2 t2);

模板函数可以轻松地针对T1和T2类型的任何组合运行不同的算法。 与虚函数实现的运行期多态不同,两种或多种类型组合调度不会产生额外的性能开销(当然,除了编写我们需要处理的所有组合的代码外)。 由此我们可以在编译期使用经典的访问者模式:

class Pet {std::string color_;
public:Pet(const std::string& color) : color_(color) {}const std::string& color() const { return color_; }template <typename Visitable, typename Visitor>static void accept(Visitable& p, Visitor& v) { v.visit(p); } // accept此时非虚
};

accept()此时是一个模板函数,同时也是静态成员函数,其第一个参数(Visitable& p)的实际类型是Pet派生类对象,它将在编译时推导出。 具体的可访问类是从基类派生的:

class Cat : public Pet {public:using Pet::Pet;
};
class Dog : public Pet {public:using Pet::Pet;
};

访问者(Visitor)不需要继承于公共基类,因为我们会在编译时解析类型:

class FeedingVisitor {public:void visit(Cat& c) {std::cout << "Feed tuna to the " << c.color() << " cat" << std::endl;}void visit(Dog& d) {std::cout << "Feed steak to the " << d.color() << " dog" << std::endl;}
};

可访问类(Visitable)可以接受任何接口正确的访问者,即层次结构中所有类的visit()方法重载:

Cat c("orange");
Dog d("brown");
FeedingVisitor fv;
Pet::accept(c, fv);
Pet::accept(d, fv);

当然,任何接受访问者参数并需要支持多个访问者的函数也都必须做成模板(拥有一个通用基类不能满足需求,它仅有助于在运行时确定实际的对象类型)。

编译期访问者同样解决了传统访问者相同的问题,它使我们能够有效地向类添加新的成员函数,而无需编辑类定义。 但看上去确实比运行期版本更出色。

访问者模式与组合模式融合

当访问者模式与组合模式模式组合在一起时,会碰撞出很多有趣的功能。C++在语言支持上,缺少了反射功能(反射指程序可以访问、检测和修改它本身状态或行为的一种能力),某些语言设计时天然具有反射能力(如java、python)。反射可以解决很多问题,例如,如果我们可以让编译器遍历对象的所有数据成员并递归地序列化每个对象,直到达到普通类型,则可以轻松解决序列化问题。我们可以使用编译期访问者模式来实现类似的功能。

用本章第一篇文章序列化反序列化的例子来讲解(构造几何图形,几何对象基类Geometry,包含三个派生类:点Point、圆Circle、线Line),其Point定义如下,此次不再继承于Geometry:

class Point {public:Point() = default;Point(double x, double y) : x_(x), y_(y) {}template <typename This, typename Visitor>static void accept(This& t, Visitor& v) { // accept是模板方法v.visit(t.x_);v.visit(t.y_);}
private:double x_;double y_;
};

上述代码中accept()方法为静态成员模板方法,此时Point不再继承于Geometry,第一篇文章中accept()是Geometry基类的虚函数,现在此函数独立于每个类中。

下面是Line的定义:

class Line { // 一条线是由两个点组成的
public:Line() = default;Line(Point p1, Point p2) : p1_(p1), p2_(p2) {}template <typename This, typename Visitor>static void accept(This& t, Visitor& v) { // accept是模板方法v.visit(t.p1_);v.visit(t.p2_);}
private:Point p1_;Point p2_;
};

Line类由两个Point类组成。 在编译时,accept()引导访问者访问每个Point。 由于我们不再使用运行时多态性,此时,容纳不同类型几何图形的容器类必须是模板:

template <typename G1, typename G2> // Intersection要操作上述定义的几何图形,所以这里必须是模板类
class Intersection {public:Intersection() = default;Intersection(G1 g1, G2 g2) : g1_(g1), g2_(g2) {}template <typename This, typename Visitor>static void accept(This& t, Visitor& v) { // accept也必须是模板方法v.visit(t.g1_);v.visit(t.g2_);}
private:G1 g1_;G2 g2_;
};

现在,我们有了可访问类型(PointLine)。 我们可以通过此接口使用不同类型的访问者,而不仅仅是序列化访问者。 但是,我们现在专注于序列化。 之前我们已经实现过一个将对象转换为字符串的访问者(第一篇文章中的StringDeserializeVisitor)。 现在,让我们将对象序列化为二进制数据流。序列化访问者可以访问某个大小的缓冲区,并将对象一次写入该缓冲区中,两次:

class BinarySerializeVisitor {public:BinarySerializeVisitor(char* buffer, size_t size) : buf_(buffer), size_(size) {}void visit(double x) { // x读到buf_中if (size_ < sizeof(x)) {throw std::runtime_error("Buffer overflow");}memcpy(buf_, &x, sizeof(x)); // 从x读值到buf_中buf_ += sizeof(x); // buf_指针后移size_ -= sizeof(x); // size_剩余大小要自减}template <typename T>void visit(const T& t) {T::accept(t, *this); // 调用可访问类型(Line、Point)的accept方法}private:char* buf_; // 数据流缓存成员变量size_t size_; // 数据流剩余大小
};

反序列化访问者从缓冲区读取内存,并将其复制到它还原的对象的数据成员中:

class BinaryDeserializeVisitor {public:BinaryDeserializeVisitor(const char* buffer, size_t size) : buf_(buffer), size_(size) {}void visit(double& x) { // 与Serialize不同,这里从buf_读到xif (size_ < sizeof(x)) {throw std::runtime_error("Buffer overflow");}memcpy(&x, buf_, sizeof(x)); // 从buf_中读值传输到x中buf_ += sizeof(x); // buf_指针读后移动size_ -= sizeof(x); // size_要减去已读字节大小}template <typename T>void visit(T& t) {T::accept(t, *this);}
private:const char* buf_; // 读入的buffersize_t size_; // 剩余未读大小
};

两个访问者都直接通过将内置类型复制到缓冲区中或从缓冲区中复制来处理内置类型,同时让更复杂的类型决定应如何处理对象。 示例代码中,如果超出缓冲区的大小,访问者会引发异常(PS:实际工程项目中,应该对buffer有扩容机制,简单的可以创建一个resize方法,当容量不够时扩展2倍或1.5倍容量)。 现在,我们可以使用访问者,例如,通过套接字将对象发送到另一台机器:

// 数据发送方操作,将图形通过BinarySerializeVisitor进行序列化
Line l = .....;
Circle c = .....;
Intersection<Circle, Circle> x = .....;
char buffer[1024];
BinarySerializeVisitor serializer(buffer, sizeof(buffer));
serializer.visit(l);
serializer.visit(c);
serializer.visit(x);// 数据接受方的操作,通过BinaryDeserializeVisitor反序列化数据
Line l;
Circle c;
Intersection<Circle, Circle> x;
BinaryDeserializeVisitor deserializer(buffer, sizeof(buffer));
deserializer.visit(l);
deserializer.visit(c);
deserializer.visit(x);

尽管C++语言本身无反射机制,但是我们可以让类以有限的方式(例如这种复合访问模式)对其内容进行反射。

有几点需要我们注意:

首先,如果仅有一个成员方法需要调用,要使用函数调用语法来调用对象本身。该约定规定visit()成员函数应改为operator()

class BinarySerializeVisitor {public:void operator()(double x); // 仅有visit()函数,可以用operator()操作符代替void visit(double& x)template <typename T> void operator()(const T& t); // 同理,用operator()代替void visit(T& t)......
};

此时,可访问对象对用访问者方式如下:

class Point {public:static void accept(This& t, Visitor& v) {v(t.x_); // 代替v.visit(t.x_)v(t.y_);}......
};

第二,用包装函数来调用多个对象上的访问者也会变得方便很多:

SomeVisitor v;
Object1 x; Object2 y; .....
visitation(v, x, y, z); // 调用包装函数,包装函数实现见下片代码

包装函数可使用可变参数模板实现:

template <typename V, typename T>
void visitation(V& v, T& t) {v(t);
}template <typename V, typename T, typename ... U>
void visitation(V& v, T& t, U& ... u) {v(t);visitation(v, u ...); // 嵌套调用
}

总结

  1. 本章我们学习了访问者模式。经典访问者模式可以在访问者类中添加虚函数新增功能而无需改变被访问对象的代码。但是在添加新的被访问对象时,需要重新编译所有访问者。非循环访问者(Acyclic Visitor)通过多重继承解决了该问题(见本章第二篇文章),并且非循环访问者还可以通过选择继承哪些访问者来对被访问对象进行组合。经典访问者必须声明所有被访问对象,无法进行自定义组合。
  2. 对于所有访问者变体,我们需要在扩展性方面进行权衡,因为变体会弱化封装,并且需要授予外部访问者类访问私有数据成员的权限。
  3. 访问者模式通常与其他设计模式(尤其是组合模式)结合使用(参见本文),以创建复杂的可访问对象。组合对象将访问委托给它包含的对象。当必须将对象分解成最小的构造块时,这种组合模式相当有用。 例如用于序列化/反序列化。
  4. 经典的访问者模式在运行时实现了双重调度:在执行过程中,程序根据访问者的类型和可访问对象的两个因素选择要运行的代码。可以在编译时类似地使用该模式,因为它提供了有限的反射功能(参见本文)

至此,本书所有内容完结!谢谢大家的观看!

github private链接访问_Hands-On Design Patterns With C++(十八)访问者模式与多分派(下)...相关推荐

  1. github private链接访问_将github配置为图床+PicGo配置

    将github配置为图床+PicGo配置 快乐的红领巾:​zhuanlan.zhihu.com 1.建一个git仓库 2.获取授权token 3.配置PicGo 4.picgo快捷键 1.建一个git ...

  2. github private链接访问_如何判定一段内存地址是不可访问的?

    地址保护 对于虚拟地址,我们知道并不是所有的地址都是可以访问的,一般分成可读,可写,可执行以及这几种的组合,或者不可访问. 违反这个规定进行内存访问,会导致程序崩溃退出,情况相当严重. 我们先从如何设 ...

  3. github private链接访问_如何将Jenkins链接到私有Github存储库?

    我的目标是将私有Github存储库拉到Jenkins . 以下是一些背景: MacOS Jenkins在本地托管, localhost:8080 在Jenkins中安装了Git插件和Github插件 ...

  4. yii2通过url访问类中的方法_行为型设计模式 访问者模式

    author zong email zongzhe1996@163.com 介绍 在访问者模式中,通过使用一个访问者类,可以改变元素类(被访问者)的执行算法.元素类的执行算法可以随着访问者的改变而改变 ...

  5. java的string访问某个元素_C#深究.net常用的23种设计模式之访问者模式(Vistor Pattern)...

    一.引言 在上一篇博文中分享了责任链模式,责任链模式主要应用在系统中的某些功能需要多个对象参与才能完成的场景.在这篇博文中,我将为大家分享我对访问者模式的理解. 二.访问者模式介绍 2.1 访问者模式 ...

  6. Java23中设计模式(Design Patterns)详解

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复 ...

  7. Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(中篇) (转)...

    Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(中篇) (转)[@more@] D ...

  8. 艾伟_转载:C# Design Patterns (4) - Proxy

    本帖介绍 Proxy Pattern (代理模式). Proxy Pattern (代理模式) The Proxy Pattern provides a surrogate or placeholde ...

  9. 17岁高中生详述如何攻破 GitHub Private Pages 并获$3.5万赏金

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 本文作者是一名17岁的美国高中生,在本文中他详述了自己和16岁的好友@ginkoid如何发现并报告 GitHub Private Pa ...

最新文章

  1. 关于tomcat无法启动问题详解
  2. 百练OJ:3681与2796:数字求和
  3. MySQL下使用Inplace和Online方式创建索引的教程
  4. vue从入门到精通之高级篇(一)vue-router的高级用法
  5. SkyWalking配上告警更优秀
  6. 联邦知识蒸馏概述与思考(续)
  7. HTML新增便签source语义,互联网的原理,常用标签,标签分类,HTML杂项,CSS
  8. 大公司都有哪些开源项目~~~简化版
  9. QT 打开选择文件对话框
  10. i5和i7哪个适合计算机网络,i5和i7的区别
  11. 年终总结系列1:基于IFRS9的预期损失准备金
  12. 3315 时空跳跃者的魔法
  13. 服务条目与采购订单、采购申请、工单、项目及WBS的关系
  14. 设置网页只允许在手机微信浏览器打开
  15. 两点之间的最短距离是?
  16. candence16.6出现license 类似retrieval of allegro_pcb_design_gxl的问题
  17. Linux系统内部流量转发机制,使用TC实现基于Linux系统的流量管理
  18. 超详细Seaborn绘图 ——(一)barplot
  19. 获取中文拼音的首字母,目前为止小菜看到的最简单的方法
  20. 群晖用php装aria2,NAS群晖DSM5.2小白教程:一行命令用 Docker 架设 aria2 服务 首发

热门文章

  1. android8.0以上新增Camera(七)
  2. Go本地浏览Web服务器
  3. all warnings being treated as errors报错解决
  4. 新冠肺炎病毒(Covid-19)检测系统
  5. 判断显卡好坏的步骤、新买显卡装机后不亮解决
  6. python3源代码分析_分析一点python源代码
  7. 扑克牌图片一张一张_扑克牌玩法 | 简单易上手的扑克游戏,重点是你没玩过!...
  8. java scene_Java Scene類代碼示例
  9. ubuntu linux 系统搭建我的世界基岩版 私服我的世界服务器
  10. mysql 存储过程游标 循环输出select 查询结果