【干货回顾】李建忠老师在 Boolan 直播间和大家畅聊《C++ 软件设计与模式的道法术》,精辟囊括了《软件设计思想、方法与模式》C++课程的主要内容,以下为本期直播干货重点:
讲师介绍李建忠
Boolan首席软件专家
Boolan首席软件专家。对面向对象、设计模式、软件架构、技术创新有丰富经验和深入研究。曾主讲《设计模式纵横谈》,《面向对象设计》网络课程,影响近百万软件开发人员,享有盛誉。全球C++及系统软件技术大会主席。曾于2005年-2010年期间担任微软最有价值技术专家,区域技术总监。拥有近二十年软件技术架构与产品经验,为包腾讯、微软、SAP等众多世界500强公司在内的知名品牌提供高端IT技术咨询和教育服务,广受好评。
软件设计能力的七个维度李建忠老师首先将C++软件开发划分为五个进阶层次:
应用开发-> 类型设计-> 框架设计 -> 系统设计 -> 平台设计
对应这五个进阶层次,其中最核心的是下面软件设计能力的七个维度:
1.设计范式:面向过程、面向对象、泛型编程、函数式编程、模板元编程;
2.设计原则:SOLID五大原则、KISS原则、DRY原则、迪米特原则
3.设计习语:RAII、NVI、Pimpl、Policy、EBCO等
4.设计规范:C++ Core Guidelines、Google C++ Style Guide等
5.设计模式:工厂、观察者、策略、模板方法等
6.架构模式:MVC、分层架构、微内核、管道-过滤器等
7.架构风格:微服务架构、组件化架构、云原生等
其中设计习语(Design Idioms)和设计模式(Design Patterns)是C++研发骨干软件设计能力的分水岭,也是从应用开发和类型设计,不断进阶到框架设计、以及系统设计的核心能力要求。
软件设计的重要原则正交设计四原则
消除重复性
分离关注点
减少不必要的依赖
向稳定的方向依赖
SOLID 五大设计原则
S:SRP 单一职责原则
O:OCP 开放封闭原则
L:LSP 里氏替换原则
I:ISP 接口隔离原则
D:DIP 依赖倒置原则
设计习语 Design Idioms设计习语描述了与特定编程语言相关的低层模式,技巧,惯用法。C++语言博大精深,有很多设计习语,它们和设计模式互为影响,呈现一种“你中有我、我中有你”的演化态势。如果只学设计模式,不学设计习语,是容易不得要领的。下面列了一些非常流行的习语,也是资深的C++程序员经常使用的。
RAII / Scope Guard 资源获取即初始化/ 范围守卫
Scope Guard 借用了 RAII 的机制实现了范围守卫。
Pimpl 指向实现的指针
很多设计模式中都能看到 Pimpl 的影子,因为面向对象的很多设计模式就是多态机制在类里的各种组合应用,Pimpl 本质也利用了多态。
CRTP 奇异递归模板模式
CRTP 代表编译时多态,在父类里通过编译时注入子类信息;虚函数代表运行时多态,两者各有千秋。
Policy Design 策略设计
Policy Design 是泛型编程领域中极其重要的一种模式,甚至在面向对象领域,如果用泛型来改造诸如 strategy、command 、观察者模式等,都能看到 Policy Design 的影子。Policy Design 的核心是对行为的正交设计,引申的 Traits Design是对类型信息的正交设计。
Mixin 混入类
在泛型领域,Mixin 替代某些面向对象、Decorator 等都是特别有效的。
SOO 小对象优化
由于堆内存分配和释放的代价,以及内存碎片效应,C++发展出来了小对象优化的技术,即通过将大量小对象放在特定的区域(例如对象池、或者栈缓存),从而避免堆内存带来的开销。
Local Buffer 本地缓存
Local Buffer是一种特殊的小对象优化技术,将小对象放在栈缓存区,只有对象超过一定尺寸,才放到堆上。标准库中的std::string、std::any、std::function 等很精巧的内部设计都用到了 Local Buffer。
Copy-On-Write(COW) 变更时拷贝
Facebook Folly库里的 FBString,当字符串大于 255 个字符时用了 Copy-On-Write,字符短的时候用 Local Buffer,当字符在 24-255 之间时用的是直接拷贝。C++ 11之前,GCC 用的是 Copy-On-Write,其他如 微软VC、LLVM 用的是小对象优化。自 C++ 11之后的编译器都实现了小对象优化。
Type Erasure 类型擦除
std::any、std::function、shared_ptr 中用得很多。
设计模式 Design Patterns历史性著作《设计模式:可复用面向对象软件的基础》一书中文版于1998年出版,其中描述了23种经典面向对象设计模式,创立了模式在软件设计中的地位。它的思想无疑是不过时的,但由于后续多次再版也没有更新内容,其中的C++手法显然已经过时了,比如没有很好地使用现代 C++ 的很多机制:智能指针、移动语义、Lambda 表达式、泛型编程等。特别在C++领域,泛型编程这些年发展、并更新了很多设计模式。
对于设计模式的分类,李建忠老师指出GOF “创建型、结构型、行为型”这种三分法对于学习和应用方面的帮助不是非常有效。李老师从2009年在企业咨询和培训过程中就提出新的分类方法,即“从管理变化的角度理解设计模式”:
晚期扩展:Template Method、Builder
父类给子类留扩展点,或者是一个类里的类型参数用模板参数的方式留扩展点,也可以借 C++ 20 中的 Concept 来约束这个模板参数,就可以实现接口规范。因此这里也有泛型的设计模式演化。
策略对象:Strategy、Observer / Event
一个策略对象用 OO 的方式表达就是一个虚函数包成的虚基类,用泛型的方式表达就是一个函数对象或 Lambda 表达式,或者是用 std::bind,std::mem_fn 这种适配器绑定的函数对象,或者是 std::function 多态的函数可调用物,甚至是一个函数指针等等。
对象创建:Factory Method、Abstract Factory、Prototype
对象创建模式主要是解决new带来的依赖问题,Factory Method是Abstract Factory的一个特例,而Prototype又是Factory Method的一个特例。
单一职责:Decorator、Bridge
这几个模式都是职责方面出的问题, 如果责任划分得不清晰,使用继承得到的结果往往出一些问题,这时候组合优于继承的特点就显现出来了。
行为变化:Command、Visitor
“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。泛型版的 visitor 直接拿模板来实现 visitor,现代C++里也提供了用 std::visit,加上 Lambda overload,在编译时实现重载辨析,而传统面向对象的 visitor 是在运行时进行的,两者对比起来非常有味道。
接口隔离:Adapter、Proxy、Façade、Mediator
添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。面向对象版的 Adapter 和 泛型版 Adapter 也有很大差异,比如 STL 容器适配器,迭代器适配器,函数对象适配器等等都超越了大家对面向对象版适配器的认知。
对象性能:Singleton、Flyweight
面向对象很好地解决了“抽象”的问题,但有时候大量对象带来的性能问题还需要很技巧性地处理。
数据结构:Composite、Iterator、Chain of Responsibility
将一些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问。
状态变化:State、Memento
如何将状态变化进行有效的管理?同时又维持高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
领域规则:Interpreter
在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案。
六大设计重构技法继承 VS. 组合
静态 VS. 动态
早绑定 VS. 晚绑定
分解 VS. 抽象
编译时多态 VS. 运行时多态
紧耦合 VS. 松耦合
设计习语与设计模式成长之路
“手中无剑,心中无剑”:见模式而不知
“手中有剑,心中无剑”:可以识别模式,作为应用开发人员使用模式
“手中有剑,心中有剑”:作为框架开发人员为应用设计某些模式
“手中无剑,心中有剑”:忘掉模式,只有原则和技法
设计模式经验之谈
代码质量优于模式
不要为模式而模式
关注抽象类 & 接口
理清变化点和稳定点
审视依赖关系
理清编译时多态(泛型)和运行时多态(面向对象)
要有Framework 和 Application的区隔思维
良好的设计是演化的结果
设计模式不能等同于算法,学习设计模式要从解耦合的管理变化的思路去学习。有些习语和模式应用还是比较广泛的,在很多著名的类库里,如 STL、Boost、Folly、Qt、Tensorflow、LLVM 等都能看到这些设计模式的应用。
C++软件设计与模式的道、法、术一个宗旨
两种思维
三大设计范式
五大设计原则
六大重构技法
十大设计习语
二十三种设计模式
文章看的不过瘾,获取回放视频


