当前位置: 首页 > news >正文

跟我学c++中级篇——面向切片编程

一、面向切片

AOP,Aspect Oriented Programming,面向切片(面)编程,其实就是一种函数式编程的衍生范型。从名字上其实还是比较容易理解的,如果说分层是从横向切面,那么AOP则是面向纵向切面,也就是说,每个大的功能模块,可以纵向切成多个片(面),在这个片里可以动态的增加处理功能,它可以通过代理接口或者预编译的方式来实现。通过业务逻辑的隔离实现解耦合。
举一个简单的例子,如果有几组类似的功能类,比如Array0中有三个类,Array1中有四个类,而Array2中有五个类(…)。这几个类都需要在进入前进行一个特定的处理而这个处理每组是相同的,组之间的处理是相似的。那么就可以对每组进行切片,然后抽象切片,完成对这些组及组内类的处理。
有过Java中Spring编程的经验的同学应该非常容易明白上面说的意思。面向切片编程有几个概念需要了解:
1、Aspect:其实就是面,在编程里可以看做类,它包含一些Pointcut和Advice
2、Pointcut:一组Join Point,即Advice发生的地方
3、Join Point:可以理解成接口调用点
4、Advice:可以理解成Join Point动作的内容,例如Spring中的before,after等前后执行的相关代码
其它还有一些概念,如target,weaving,introduction,proxy等,大家可以参考相关书籍和资料。

二、作用

面向切片和面向对象以及其它什么面向范式,都有着具体的应用场景,还是那句话,包打天下的打手是不存在的。不要在这些无意义的互相替换或者哪个更优上浪费生命。其实无论哪种编程,站到思想的高度来看,其实都是一种思想的解放或者说认知的扩展,这才是最重要的。面向切片有可能和一些设计模式有些类似,但这无关紧要,本身设计模式也是一种设计思想的体现。面向切片更注重的是切出共同的行为形成一种接口可共用,这才是重点。所有的思想设计最终都是为了让软件的设计、开发更符合原则。
面向切片编程可能更注重的是各个模块对象间的抽象的关系特别是动作的关系,也可以理解成纵向的分层。在这一点上,和DDD的设计原则在业务逻辑上的处理有一种相通的意思。

三、实例

在前面的模板编程里,提到了一个耗时处理的模板函数,其实就是面向切片编程的方式。在不同的函数和不同的类中,可能都需要这个耗时函数,那么最简单的方法就是抽象出一个时间函数,在类或者函数中调用。但是这样的调用,往往会引入重复的代码,这时就可以考虑切片编程了。看下面的例子:

auto TestTime(int a,int b)
{struct timeval t1, t2;double timeuse;gettimeofday(&t1, NULL);//todo:work timegettimeofday(&t2, NULL);timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) / 1000000.0;printf("Use Time:%f\n", timeuse);
}

这是一个很常见的计算耗时的方法,如果每个函数或者类对象中都有这么个计算过程,大量的重复代码和复杂的维护成本都让人想想就难受。那么能不能按照上面的AOP原则进行切片呢?首先要抽象的是公共的方法,都是要计时,计时又需要两部分,即动态的处理需要计时的函数,以及另外一个计时的过程。好,这样就可以先抽象第一步,即把计时的函数进行控制,这就用到了以前那个计时处理的函数:

template <typename Fn, typename... Args>
void TestTime(Fn &fn, Args... args)
{struct timeval t1, t2;double timeuse;gettimeofday(&t1, NULL);fn(std::forward<Args>(args)...);gettimeofday(&t2, NULL);timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) / 1000000.0;printf("Use Time:%f\n", timeuse);
}

第二步,可不可以把gettimeofday这种重复的代码封装起来。这时候儿很容易想到什么 ?RAII,C++中对付这种最简单的最直接的方式。时间的计算以构造函数开始,以析构函数结束,恰恰是目前所需要的,看下面的代码:

#include <cstddef>
#include <stdio.h>
#include <sys/time.h>class CaclTime {
public:CaclTime() { gettimeofday(&tStart_, NULL); };~CaclTime() {gettimeofday(&tEnd_, NULL);timeuse_ = tEnd_.tv_sec - tStart_.tv_sec + (tEnd_.tv_usec - tStart_.tv_usec) / 1000000.0;printf("Use Time:%f\n", timeuse_);}private:struct timeval tStart_;struct timeval tEnd_;double timeuse_ = 0.0;
};template <typename Fn, typename... Args>
void TestTime(Fn &fn, Args... args)
{CaclTime();fn(std::forward<Args>(args)...);
}

把这两个点搞定,再往上层抽象其实就简单了。主要考虑有三类:
1、切片功能类和其进一步的抽象类,见上面的类
2、业务类,即谁调用切片功能类
3、抽象代理类,即把上面的计算通过代理来实现进一步的抽象


void Run(int num) {for (int i = 0; i < num; i++) {// todo}
}
template <typename T> struct TimeProxy {
public:template <typename F, typename... Args> static decltype(auto) Task(F f, Args &&...args) {T t;return f(std::forward<Args>(args)...);}
};
TimeProxy<CaclTime>::Task(Run, 100);//注意,实际使用中必须在模板实例化后才可使用,比如函数中,否则报错“requires template<> synax”

而为了处理多个切片,可以抽象代理类:

//利用变参模板和tuple的特性来获取指定的类型
template<typename ...Types>
class ProxyObjectTypes {public:template<std::size_t ID>struct GetType {using Type = typename std::tuple_element<ID, std::tuple<Types...>>::type;};
};

那么下来就可以通过Tuple来动态获取类型并展开:

template <typename... Types> struct ProxyTypes<ProxyObjectTypes<Types...>> {template <typename F, typename... Args> static decltype(auto) Process(F &&f, Args &&...args) {size_t num = sizeof...(Types);for (int i = 0; i < num; i++) {using Tfunc = typename ProxyObjectTypes<Types...>::template GetType<i>::Type;Tfunc tf;}return std::forward<F>(f)(std::forward<Args>(args)...);}
};

可是这个不符合现在在编译期展开的小潮流,那么用什么呢?std::make_index_sequence来处理:

template <typename... Types> struct ProxyTypes<ProxyObjectTypes<Types...>> {template <typename F, typename... Args> static decltype(auto) Process(F &&f, Args &&...args) {return Call(std::make_index_sequence<sizeof...(Types)>{}, std::forward<F>(f), std::forward<Args>(args)...);}template <std::size_t ID, typename F, typename... Args>static decltype(auto) Call(std::index_sequence<ID>, F &&f, Args &&...args) {using Tfunc = typename ProxyObjectTypes<Types...>::template GetType<ID>::Type;Tfunc tf;return std::forward<F>(f)(std::forward<Args>(args)...);}template <std::size_t ID, std::size_t... R, typename F, typename... Args>static decltype(auto) Call(std::index_sequence<ID, R...>, F &&f, Args &&...args) {using Tfunc = typename ProxyObjectTypes<Types...>::template GetType<ID>::Type;Tfunc tf;return Call(std::index_sequence<R...>{}, std::forward<F>(f), std::forward<Args>(args)...);}
};

当然,在最后展开时,也可以使用前面学习过的std::initializer_list和逗号展开方法,不过这样做需要重新写一下上面的代码,有兴趣可以自己搞一下。上面的代码虽然可以使用,但是如果在上面的抽象中再增加一些SFINAE的控制或者C++20 的concepts的处理,就更安全了。
最后,使用这种切片编程,需要关注性能的要求,到达一定程度后,可能就需要改变方式来处理。如果想进一步的抽象封装还可以通过基础的宏定义来完成,这些都不难,有兴趣可以自己搞一搞。另外这个代码整体的流程都没有支持类成员变量,如果想支持只需要增加相关的参数和设置即可。

四、总结

所有的设计思想和编程模式,都是一种抽象。既然是一种抽象,就意味着从思想的高度来看问题,不能纠结于具体的某个实现细节。思想是一种指导行为的原则,而不是指导行为的行动过程。很多人往往忽视了这一点。所以在对待编程思想和设计思想上,会有两种论调,一种是无用论,不管是看得懂还是装看不懂,觉得这种思想没啥实际作用,不如实际写点代码有用;另外一种是空谈论,坐而论道,不顾实际,觉得写代码特别LOW。正如前面反复强调的,计算机知识是一种高度的理论和实践相结合的知识,理论和实践是一种互相促进互相制约的一种状态 。
在计算机技术上,知行合一,更体现的淋漓尽致。

相关文章:

  • 单片机学习记录(一)
  • 深度学习笔记(二)——Tensorflow环境的安装
  • ZooKeeper 实战(五) Curator实现分布式锁
  • 网络命令ping和telnet
  • electron桌面应用开发——快速入门教程
  • MongoDB-数据库文档操作(2)
  • 【GCC】6 接收端实现:周期构造RTCP反馈包
  • debian12部署Gitea服务之二——部署git-lfs
  • 2024年软考考试时间确定了!请收好
  • 连接世界:2024 年 5G 及未来技术趋势
  • Vue3+ElementPlus实例_select选择器(不连续搜索)
  • MySQL、Oracle 常用SQL:建表、建视图、数据增删改查、常用condition
  • WPF Converter转换器
  • 如何将github copilot当gpt4用
  • 8. 《自动驾驶与机器人中的SLAM技术》基于保存的自定义NDT地图文件进行自动驾驶车辆的激光定位
  • [译] React v16.8: 含有Hooks的版本
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • 10个最佳ES6特性 ES7与ES8的特性
  • Angular Elements 及其运作原理
  • angular学习第一篇-----环境搭建
  • Github访问慢解决办法
  • Java的Interrupt与线程中断
  • Java知识点总结(JavaIO-打印流)
  • js
  • node入门
  • PHP 7 修改了什么呢 -- 2
  • vue 个人积累(使用工具,组件)
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 百度地图API标注+时间轴组件
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 聊聊flink的BlobWriter
  • 入口文件开始,分析Vue源码实现
  • 在Mac OS X上安装 Ruby运行环境
  • ​渐进式Web应用PWA的未来
  • #pragma预处理命令
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (2.2w字)前端单元测试之Jest详解篇
  • (HAL库版)freeRTOS移植STMF103
  • (Note)C++中的继承方式
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (转)视频码率,帧率和分辨率的联系与区别
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET 命令行参数包含应用程序路径吗?
  • .net通用权限框架B/S (三)--MODEL层(2)
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [BUUCTF NewStarCTF 2023 公开赛道] week3 crypto/pwn
  • [dfs] 图案计数
  • [Docker]五.Docker中Dockerfile详解
  • [EFI]Dell Latitude-7400电脑 Hackintosh 黑苹果efi引导文件
  • [HackMyVM]靶场Crossbow