【编码经验】数据结构与语法规范、计算机算法、架构模式设计、代码重构
系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
TODO:写完再整理
文章目录
- 系列文章目录
- 前言
- 编码的三个层次
- 第一层:数据结构与语法规范层
- 0、clang-format代码格式规范工具的安装及配置
- 1、安装clang-format工具
- 2、获取clang-format(LLVM)的安装路径,并添加到环境变量
- 3、配置vscode的相关配置
- 注意
- 1、什么要有代码编程的规范?
- 2、命名规范
- 1.宏命名
- 2.常量参数名
- 3.变量名(名词)
- 4.枚举名、结构体名、类名
- 5.函数名
- 6.文件名(功能包名)
- 3、函数规范
- 1.最小业务功能原则
- 2.拆分过长的函数
- 3.标准化的输入输出函数接口【重要】
- 4.消除重复的代码
- 5.理清楚嵌套的if语句【fsm】
- 6.内联函数
- 注意
- 4、注释规范
- 1.注释的原则
- 2.注释的方式
- 3.减少注释的方法
- 5、代码格式排版规范
- 6、错误处理方式
- 1.使用异常事件try/catch处理
- 2.返回错误代码errorcode(函数返回处理)
- 7、代码组织--物皆有其位,而后尽其位【分文件、分模块、设计导航架构】
- 1.【分文件】文件之间的相互包含关系
- 2.【分模块】工厂设计模式
- 3.【设计导航架构】重构系统的方法
- 8、写代码的方法
- 1.移植使用第三方代码的方法
- 2.首先是追求代码可以用,在是追求代码优雅可扩展
- 3.单元测试方法
- 9、特殊的技巧
- 1.结构体 VS 类的抽象
- 2.智能指针
- 3.auto 关键字
- 4.类型转换
- 5.nullptr 和 NULL
- 10、上传git代码的标准
- 其他参考资料
- 第二层:计算机算法层
- 第三层:架构模式设计层
- A、ROS工程规范
- 1、ROS工程目录架构规范
- (1)launch启动文件
- (2)工程架构
- 2、ROS功能包架构规范
- 3、ROS源文件节点(.cpp/,h)架构规范
- 4、ROS的msgs数据类型规范
- 5、ROS的话题名、变量名命名规范
- B、工厂模式代码设计
- 1、工厂模式的介绍
- 2、工厂模式的实现方式【复杂性逐渐提高,根据场景选用哪种模式】
- 1.简单工厂模式(尝试做)
- (0)使用场景
- (1)简单工厂模式的结构组成【放在一个文件夹或者功能包】
- (2)简单工厂模式的特点
- (3)简单工厂模式的缺陷
- (4)简单工厂模式的代码实现
- (1)ShoesFactory工厂类代码实现(创造基类xxx_base.h)
- (2)Shoes鞋子的抽象类(基类xxx.h)
- (3)具体产品类(类的实例化xxx.cpp)
- 2.工厂方法模式(专业做)
- (0)使用场景
- (1)工厂方法模式的结构组成
- (2)工厂方法模式的特点
- (3)工厂方法模式的缺陷
- (4)工厂方法模式的代码实现
- (1)抽象工厂类(ShoesFactory)
- (2)具体工厂类(NiKeProducer、AdidasProducer、LiNingProducer)
- (3)具体产品对象【xxx.cpp】(NiKeShoes\AdidasShoes\LiNingShoes)【调用阶段了】
- 3.抽象工厂模式(泛化做)
- (0)使用场景
- (1)抽象工厂模式的结构组成(和工厂方法模式一样,只是成员更多了)
- (2)抽象工厂模式的特点
- (3)抽象工厂模式的缺陷
- (4)抽象工厂模式的代码
- (1)抽象工厂类厂(Factory)
- (2)具体工厂类(NiKeProducer)
- (3)抽象产品类(Shoes\Clothe)
- (4)具体产品类(NiKeShoes\NiKeClothe)
- main函数调用
- 4.模板工厂(大一统)
- (1)原理
- (2)优缺点
- (3)模板工厂的代码实现
- (1)抽象模板工厂类(AbstractFactory)
- (2)具体模板工厂类(ConcreteFactory)
- (3)抽象产品类(Shoes\Clothe)
- (4)具体产品类(NiKeShoes\NiKeClothe)
- main函数调用
- 5.产品注册模板类+单例工厂模板类(超大型工程->一般用不到了)
- 总结
- 工厂模式是一种思想
- 工厂模式优点
- 参考资料
- 第四层:代码重构
- 1、重构的定义
- 2、重构的目的
- (1)增加代码的兼容性、拓展性
- (2)增加代码的逻辑性,减少复用及多余的流程,逻辑清晰明了
- (3)提高代码开发的效率
- (4)提高代码的可读性
- (5)【个人的】通过复盘,更好的积累自己的技术和代码储备
- (6)最终,把demo实验验证上升到一个产品
- 3、重构的挑战(难处)
- 1.意识问题
- 2.方法论问题
- 3.额外工作问题
- 4.利益问题
- 4、重构的程度及方法论【重点】
- 1.小型重构方法论【日常工作、空闲时间】
- 2.大型重构方法论【制定计划,按部就班】
- (1)系统业务结构重构
- (2)技术模块结构
- (3)代码数据流结构
- 3.重构技巧
- 4.重构的效果到适度就好(定重构的需求)
- 5、避免经常重构的方法
- 6、大型代码重构流程【按部就班,一步一步推进】
- 1.事前【目标计划】
- 2.事中
- 3.事后【沉淀形成收获】
- 参考资料
前言
认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长!
本文先对编码经验做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章
提示:以下是本篇文章正文内容
编码的三个层次
1、第一层:数据结构与语法规范层
2、第二层:计算机算法层
3、第三层:架构模式设计层
4、第四层:代码重构
其中,第一与第二层都是熟能生巧,创新的东西不多靠多练习就可以掌握。第三层需要结合具体场景业务去做设计思考与积累才能总结出行业产品常用的架构!
第一层:数据结构与语法规范层
有关C/C++语言、python语言的相关语法我在另外一个专栏有记录,这里仅仅记录相关编码规范
0、clang-format代码格式规范工具的安装及配置
1、安装clang-format工具
(1)在win下安装clang-format(或者安装一个总包LLVM),下载链接如下:
https://zhuanlan.zhihu.com/p/380290758
https://github.com/llvm/llvm-project/tags
(2)在Linux下可以直接通过指令行安装,参考链接如下:
https://blog.csdn.net/shenmingxueIT/article/details/122125191
.
.
2、获取clang-format(LLVM)的安装路径,并添加到环境变量
方法一:配置vscode的这一栏
方法二:直接在win中添加环境变量
win添加环境变量PATH的方法
https://www.xitongzhijia.net/xtjc/20220406/243840.html
.
.
3、配置vscode的相关配置
打开VS Code,安装如下两张图进行配置,实现每次编辑后保保存代码会自动按照上述风格格式化,且只格式化刚修改之处(注意最新C/C++插件已经原生支持调用clang,因此不需要再额外安装VS Code上的clang插件,如已安装请卸载)
第一个和第三个是设置标准的代码格式规范,当然也可以使用自己设置的格式规范(需要自己生成.clang-format格式文件),方法如下:
https://blog.csdn.net/niu_88/article/details/120919043
https://blog.csdn.net/weixin_43360707/article/details/125426062
第二个是设置clang-format的环境变量位置(就是clang-format安装的位置)
第一个是设置保存文件时自动化进行格式化
第二个是设置自动格式化时时对整个文件进行格式化,还是仅仅对自己编写的代码进行修改
其他参考链接:
https://blog.csdn.net/weixin_43360707/article/details/125426062
https://blog.csdn.net/shenmingxueIT/article/details/122125191
https://blog.csdn.net/mingshili/article/details/120576171
注意
1、Clang_format已经被vscode集成了不需要再安装clang-format这个插件了
2、Clang是一个C语言、C++、Objective-C、C++语言的轻量级编译器,但是clang-format是代码格式化的工具
.
.
1、什么要有代码编程的规范?
有编程规范有利于代码的快速阅读与更改,方便后续的二次开发与维护
.
.
2、命名规范
1.宏命名
常量参数优先用宏定义,出现数字是很难被理解的,一些需要进行调节的参数必须定义为#define的形式,不然不直观,甚至很难进行更改!
宏的命名单词全部大写,由下划线连接
#define PI_ROUNDED 3.14
.
2.常量参数名
命名时以“k”开头,大小写混合
const int kDaysInAWeek = 7;
.
3.变量名(名词)
命名的首要原则是名副其实,每个命名都要力求有意义且简洁,函数的命名一般使用动名词。变量的的命名一般使用名词,变量(包括函数参数)和数据成员名一律小写,单词之间用下划线连接。
int local_variable,struct_data_member;
全局变量后加下划线,局部变量后不加下划线
.
4.枚举名、结构体名、类名
类名和对象名应该是名词或者名词短语,且开头大写,多名词组合的独立名词首字母也要大写,不包含下划线,如
class MyClass {}
struct MyStruct {}
.
5.函数名
函数名(方法名)应该是动词或者动名词,且开头动词小写,动词与名词组合的名词大写,单词之间不使用下划线。
addTwoNumber()
getSum()
.
6.文件名(功能包名)
文件名要全部小写,以下划线”_”连接不同单词。不要使用已经存在与/usr/include 下的文件名。
.
.
3、函数规范
1.最小业务功能原则
一个函数只做一件事情,把函数设计成实现一个最小功能的单元就好,只做一件小事情,多个小事情组合成为一个大事情。
.
2.拆分过长的函数
(1)一个函数代码量尽量在30行左右为佳。函数越长阅读起来越费劲,迁移和组合难度越大!功能越死板改动一下功能都更难。如果函数超过30 行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。
(2)各个函数是嵌套关系的,因为阅读代码一般是自顶向下阅读的,因此每个函数都要归为一类,一般分为:底层的实现函数、中间层的逻辑组合函数、顶层的业务功能函数三类,进行函数的组合。
.
3.标准化的输入输出函数接口【重要】
1、函数的参数顺序
输入参数在先,后跟输出参数。
2、输入参数若是在函数中不可改变的,尽可能使用关键字const修饰
const 关键字为数据成员在编译时类型检测增加了一层保障; 便于尽早发现错误。如果函数不会修改你传入的引用或指针类型参数,该参数应声明为const。
如一般输入的变量在函数中不能被改变的(如定位的pose)必须用const进行修饰。
3、输出参数要么使用指针“*”、要么使用引用“&”,后者更佳
输出参数可以使用引用“&”和指针“*”
4、函数的返回数据类型
bool类型或者枚举类型的输出用函数类型进行return,这样做那一条函数出现问题可以快速定位.一般的函数尽量不要定义成void类型的,至少定义成一个bool类型的,或者是枚举errorcode类型的。没有返回值的函数挂了也检测不出来。
6、初始化列表的方法
若传入对象比较复杂,可以传入参数列表,本质还是用一个更大的数据接口包含一系列小的变量
7、工厂函数的接口
定义类的虚函数,甚至是纯虚函数。
.
4.消除重复的代码
若函数太长,或者函数有较多重复的函数调用,这时候就要考虑函数的封装了使得函数更加能够理解读懂,使得代码不要那么臃肿,同时保证代码短小精简
.
5.理清楚嵌套的if语句【fsm】
串联逻辑用if/else,并联逻辑用switch/case
.
.
6.内联函数
只有当函数只有 10 行甚至更少时才将其定义为内联函数。通常内联函数不包含循环、switch 语句,且不是递归函数。
.
注意
(1)一般函数的输出一定要有接口,函数不依赖任何全局变量(不要在外面定义一个全局变量,在函数内使用全局变量达到参数传递的效果)
(2)若是输入或者输出的参数较多,且参数都属于一类的数据,可以定义一个类,并用这个类实例化一个对象,把对象作为参数传进去函数。函数就可以访问对象里面的所有成员。
(3)尽量编写一元函数和二元函数,传入传出参数过多调用函数设置的时候就复杂,同时函数头也比较长不美观(传入对象而非一个参数变量能解决这个问题)
(4)区分输入参数和输出参数的直观方法:输出参数一般用const甚至没有修饰,输出参数用引用&和指针*进行标志
(5)若一行代码过长,或者函数传入的参数过多,为了格式对齐要用分隔符“/”进行隔断
.
.
4、注释规范
1.注释的原则
(1)可以理解的前提下,尽可能少的注释。
(2)有多余的注释测试完成,功能可用后要及时整理删除更新。
2.注释的方式
(1)在调试代码的时候可以写下注释,再通过测试后应该删除注释,并把原来的注释翻译简化留下基本的提醒就好。
(2)注释本质是一个提醒,边测试边修改,验证错误的注释必须马上删除
(3)注释是写程序的中间产物,程序员是不会维护注释的,注释停留的时间越长信息描述越不准确(程序在变化,注释也要跟着变化)
3.减少注释的方法
1、使用一看就懂的变量和函数命名方式,一写出来的代码就知道这个函数变量的意思
2、尽量不要去写注释的方向去想
3、代码在测试阶段还有价值就先别删掉,测试完毕没有价值的代码或者是就方案的代码就可以删除掉了,甚至定义的函数但没有被调用的函数一样可以被删除掉。
.
.
5、代码格式排版规范
代码整洁的排版需要一套简单的格式规则
(0)代码的空格和缩进要有规范
(1)注意在一行代码中代码不宜过长–使用分隔符(甚至可以直接换行)
(2)注意作用域的缩进格式–使用标准的TEB键
(3)注意代码行和代码行之间不能有空行,函数与函数之间空一行就行
(4)注意判断与赋值符号两边都要留有空格,空格字符有强调的作用
(5)函数的形参一般在括号出不留空格,在逗号后面加一个空格,强调变量的数量
(6)变量定义在符合作用域的前提下,尽量与调用位置靠近,不必集中的定义在一个地方(全局变量定义在使用它的函数前面)
(7)函数在头文件声明的位置
相关概念的函数声明应该放在一起,甚至定义成一个类。函数的共性越强,当的距离应该更近一些
(6)类和枚举的一些变量垂直应该对齐,缩进适当
(9)在while、for、if这些语句后的大括号要留下一个{,if/else相互嵌套时,if else不能分开,不然条件嵌套关系不明显
(10)如果while、for这些语句的循环是空的也要用大括号{},尽量不要只写一个分号,迷惑性极强
对象和数据结构的格式
(1)类内public的成员是别人可以用的
(2)类内privide是自己用的
(3)类内函数和类内变量要分开写,宁可多写几个public和privide的关键字
(4)类的内容应该尽可能小,类是同一性质成员的最小单元。类与类之间可以继承和友元使用,代码越长越难理解
(5)抽象出来同一层次的类,同一层次的类使用工厂设计模式!提供标准的参考模板
6、错误处理方式
1.使用异常事件try/catch处理
在try/catch中尽量不要有太多的代码,最好再进行一次函数封装,使得try/catch的结构更加清晰,示例如下:
.
2.返回错误代码errorcode(函数返回处理)
这个需要函数是errorcode的数据类型的,一般返回错误代码是用于调试输出的(看log),使用异常事件是用于系统底层做出对应动作的(看warning和error的系统信息)。程度不一样。
一般有一个Errorcode.h的文件对应
Errorcode的方式
.
.
7、代码组织–物皆有其位,而后尽其位【分文件、分模块、设计导航架构】
1.【分文件】文件之间的相互包含关系
头文件的相互包含的问题的原因
当编译出现XXX文件找不到的情况有可能是因为文件1包含了文件2,而文件2又包含了文件1,在编译解析的时候就会不断递归导致编译崩溃后,就找不到文件了
(1)当一个源文件过大的时候就应该考虑把一个源文件进行拆分。用一个更好一级的源文件include拆分的文件,整合调用
(2)一个源文件尽量仅仅使用一种编程语言,避免混编(不同编程语言的规范不太一样的)
所有头文件都应该使用#define 防止头文件被重复包含
.
2.【分模块】工厂设计模式
1、所有继承必须是 public 的。
2、必要的话,析构函数声明为 virtual。如果你的类有虚函数,则析构函数也应该为虚函数
3、数据成员都必须是私有的,将所有数据成员声明为 private,除非是 static const 类型成员。
4、不要过度使用 protected 关键字。
.
3.【设计导航架构】重构系统的方法
当想要加入一点代码变得更加难,需要考虑到复杂的逻辑的时候就要重新梳理逻辑和工程架构了
(1)先有一个大方向架构目标,再从具体的每个小模块进行重构,重构一次不完美就重构第二次,修改格式绝对不可能一次完成的
(2)系统框架应该是最简单易懂的设计,若系统架构都是难以理解的逻辑,在该系统框架下的代码肯定是更加复杂的逻辑【大道至简】
一开始就不注重代码架构设计和规范,到最后面只会越走越难。即使是推倒重来也只能快跑一段时间跑不远的(无轨迹不成方圆)
(每段代码都应该出现在你设计的该有的位置上,如果该段代码不在这里,就应该重构代码,使得代码找到他该在的位置)
分task或者节点的原因
1、每个task的运行频率不一样
2、对模块之间进行解耦,开发逻辑更清晰
(1)了解并使用嵌入式系统平台的功能【os】
(2)导航系统各个模块的解偶,对每个模块层次进行重构
(3)对公共模块进行重构
(4)开始业务流程的重构
【公司的产品业务系统和ros的仿真验证系统的构建方式是不一样的】
.
.
8、写代码的方法
1.移植使用第三方代码的方法
(1)移植使用第三方代码需要根据我们的工程情况为第三方代码在封装一些函数接口,以保证代码的边界性
(2)看懂别人的代码,最好自己手撕一次!
(3)移植的心得,除非是算法小黑盒,不然移植还是要靠自己理解了去移植,因为业务和环境不一样
.
.
2.首先是追求代码可以用,在是追求代码优雅可扩展
(1)写代码和写论文是一样的,第一步先把内容写出来,第二步再封装整理使得格式和结构达到符合规范!
(2)打磨代码的方法:分解函数、修改名称、消除重复、重新设置架构、抽象成类同时保证测试通过。
都是需要时间一关一关的过的!
.
3.单元测试方法
不同代码的测试方法不一样,只要可以通过测试的最后一关就说明生产代码是符合标准的!
(1)方法一:使用文字和指令告诉测试工程师测试方法,让可是工程师在不同场景做测试
(2)方法二:把指令封装到bash文件中,一键启动
(3)方法三:自己根据测试场景和需求去边测试用例
.
.
9、特殊的技巧
1.结构体 VS 类的抽象
仅当只有数据成员时使用 struct,其它一概使用 class。
同一作用范围和层次的变量或者函数要抽象成结构体和类
变量尽量不要定义成全局变量,如果不得已要定义成全局变量,也要把一类的全局变量封装到一个类中,实例化一个类从而定义一个全局变量,最好还要给该类修饰一个作用域namespace
.
.
2.智能指针
尽可能使用智能指针,避免内存泄露。倾向于使用 std::unique_ptr 明确所有权传递。如果要使
用共享所有权,建议使用 std::shared_ptr。不要使用 std::auto_ptr,使用 std::unique_ptr 代替它。
.
.
3.auto 关键字
auto 关键字可以自动匹配变量和函数的数据类型,用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。
.
4.类型转换
使用 C++ 的类型转换,如 static_cast<>()。不要使用 int y = (int)x
或 int y = int(x)等转换方式。
.
5.nullptr 和 NULL
整数用 0,实数用 0.0,指针用 nullptr(C++ 11),字符 (串) 用 ‘\0’。
.
.
10、上传git代码的标准
(1)标准一:经过测试后上传的代码是最精炼的,别人只在乎代码的结果,不会在乎代码的过程
(2)标准二:符合团队的编程格式要求!
其他参考资料
https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/
第二层:计算机算法层
第三层:架构模式设计层
A、ROS工程规范
1、ROS工程目录架构规范
(1)launch启动文件
Launch文件最好放在一个功能包里面,用命令行容易寻找得到,同时编程的时候工程也不用这么乱。总的launch文件搞一个robot_launch功能包,子功能包内放一个launch文件夹来存放【防盗标记–盒子君hzj】
(2)工程架构
--src--一级文件目录【按层次分】
(一级功能包......)--二级文件目录【按功能分】
--(二级功能包)
2、ROS功能包架构规范
(0)cmakelist、package.xml文件填写参数【cmake编译器相关】【防盗标记–盒子君hzj】(1)src文件夹
存放C++、python源码的空间(2)include文件夹
存放通用库lib文件或功能包(3)config文件夹
存放参数文件,如.csv/.yaml/.config(4)launch文件夹
存放调试和部署的大大小小的launch文件
存放rviz相关的文件
存放gazebo相关的文件 (5)doc/.md文件夹
存放说明文档
...
注意
(1)功能包是多个文件夹的东西一起写的,不分先后顺序,同时进行debug【防盗标记–盒子君hzj】
(2)不同的功能包可以定义相同名字的变量和函数,因为功能包名字不一样,所以不同的功能包可以定义相同名字的变量和函数,这样可以使得功能包更加连贯起来,同时功能又相互独立
3、ROS源文件节点(.cpp/,h)架构规范
(1)#include<功能包名/.h文件名>
(2).cpp构造函数编写最好是有顺序规范的
【构造函数最好带有句柄,.h文件的class也是对应这个顺序】
0)句柄
1)param变量加载
2)创建订阅器
3)创建发布器
4)创建定时器
5)创建boost线程
6)服务通讯
7)动作通讯
8)运行仅仅运行一次的初始化函数
(3).h的类:对应写声明,分门别类
注意
没有写完main()函数就会报错,因为编译找不到起点,要写完main()函数,类和构造函数逗得先写完【防盗标记–盒子君hzj】
4、ROS的msgs数据类型规范
待补充~
5、ROS的话题名、变量名命名规范
(1)在节点node中设置订阅和发布topic名字的技巧
(1)作用
可以在launch文件中更改话题的名字,修改话题名字比较方便,同时在终端debug的时候比较方便
【先使用launch文件的话题名,没有在launch文件中定义的时候使用默认的话题名】(2)方法1)在launch文件夹中用<param>标签定义一个话题名变量2)在CPP源文件的构造函数中用nh.param获取该话题名变量,然后在订阅器和发布器上填入该话题名变量即可【一开始的时候,最好默认的话题名和launch的话题名一致】
(2)话题名规范,话题名前面都加上“/”,作为话题的识别【当然不加也可以】【防盗标记–盒子君hzj】
.
.
B、工厂模式代码设计
1、工厂模式的介绍
工厂模式类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,
使用了C++多态的特性,将存在继承关系的类,通过一个工厂类创建对应的子类(派生类)对象。在项目复杂的情况下,可以便于子类对象的创建【防盗标记–盒子君hzj】
.
.
2、工厂模式的实现方式【复杂性逐渐提高,根据场景选用哪种模式】
1.简单工厂模式(尝试做)
【工厂->抽象产品->具体实现】
(0)使用场景
【用哪个功能就实例化哪个抽象类】鞋厂可以指定生产耐克、阿迪达斯和李宁牌子的鞋子。哪个鞋炒的火爆,老板就生产哪个,看形势生产【防盗标记–盒子君hzj】
.
.
(1)简单工厂模式的结构组成【放在一个文件夹或者功能包】
(1)工厂类(xxx_base.h接口)
工厂模式的核心类,会定义一个用于创建指定的具体实例对象的接口,一般是以虚方法实现
(2)抽象产品类(xxx.h接口)【防盗标记–盒子君hzj】
是具体产品类的继承的父类或实现的接口,抽象产品类是继承并扩展工厂类(xxx_base.h)
(3)具体产品类(xxx.cpp实现)
工厂类所创建的对象就是此具体产品实例
.
.
(2)简单工厂模式的特点
工厂类封装了创建具体产品对象的函数
.
.
(3)简单工厂模式的缺陷
扩展性非常差,新增产品的时候,需要去修改工厂类【防盗标记–盒子君hzj】
.
.
(4)简单工厂模式的代码实现
举例
(1)ShoesFactory是较为通用的工厂类【xxx_base.h】
(2)Shoes是抽象产品类【xxx.h】
(3)NiKeShoes\AdidasShoes\LiNingShoes是具体产品类【xxx.cpp】【防盗标记–盒子君hzj】
(1)ShoesFactory工厂类代码实现(创造基类xxx_base.h)
类里实现根据鞋子类型创建对应鞋子产品对象的CreateShoes(SHOES_TYPE type)函数
代码实现
enum SHOES_TYPE
{NIKE,LINING,【防盗标记–盒子君hzj】ADIDAS
};// 总鞋厂
class ShoesFactory
{public:// 根据鞋子类型创建对应的鞋子对象Shoes *CreateShoes(SHOES_TYPE type){switch (type){case NIKE:return new NiKeShoes();break;case LINING:return new LiNingShoes();break;case ADIDAS:return new AdidasShoes();break;default:return NULL;break;}}
};
(2)Shoes鞋子的抽象类(基类xxx.h)
Shoes为鞋子的抽象类(基类),接口函数为Show(),用于显示鞋子广告。【防盗标记–盒子君hzj】
NiKeShoes、AdidasShoes、LiNingShoes为具体鞋子的类,分别是耐克、阿迪达斯和李宁鞋牌的鞋,它们都继承于Shoes抽象类
代码实现
// 鞋子抽象类
class Shoes
{public:virtual ~Shoes() {}virtual void Show() = 0;
};// 耐克鞋子
class NiKeShoes : public Shoes
{public:void Show(){std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl;}
};// 阿迪达斯鞋子
class AdidasShoes : public Shoes
{public:void Show(){std::cout << "我是阿迪达斯球鞋,我的广告语:Impossible is nothing" << std::endl;}
};// 李宁鞋子
class LiNingShoes : public Shoes
{public:void Show(){std::cout << "我是李宁球鞋,我的广告语:Everything is possible" << std::endl;}
};
(3)具体产品类(类的实例化xxx.cpp)
main函数,先是构造了工厂对象,后创建指定类型的具体鞋子产品对象,创建了具体鞋子产品的对象便可直接打印广告。因为采用的是new的方式创建了对象,用完了要通过delete 释放资源资源哦!【防盗标记–盒子君hzj】
代码实现
int main()
{// 构造工厂对象ShoesFactory shoesFactory;// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE);if (pNikeShoes != NULL){// 耐克球鞋广告喊起pNikeShoes->Show();// 释放资源delete pNikeShoes;pNikeShoes = NULL;}// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING);if (pLiNingShoes != NULL){// 李宁球鞋广告喊起pLiNingShoes->Show();// 释放资源delete pLiNingShoes;pLiNingShoes = NULL;}// 从鞋工厂对象创建阿迪达斯鞋对象Shoes *pAdidasShoes = shoesFactory.CreateShoes(ADIDAS);if (pAdidasShoes != NULL){// 阿迪达斯球鞋广告喊起pAdidasShoes->Show();// 释放资源delete pAdidasShoes;pAdidasShoes = NULL;}return 0;
}
.
.
2.工厂方法模式(专业做)
(0)使用场景
【每个功能都很重要,都要为其实例化抽象类】现各类鞋子抄的非常火热,于是为了大量生产每种类型的鞋子,则要针对不同品牌的鞋子开设独立的生产线,那么每个生产线就只能生产同类型品牌的鞋【防盗标记–盒子君hzj】
.
.
(1)工厂方法模式的结构组成
(1)抽象工厂类【xxx_base.h】(ShoesFactory)
工厂方法模式的核心类,提供创建具体产品纯虚函数的接口,由具体工厂类实现。
(2)具体工厂类【xxx.base.cpp】(NiKeProducer\AdidasProducer\LiNingProducer)
继承于抽象工厂,实现创建对应具体产品对象的方式。
(3)抽象产品类【xxx.h】(Shoes)
它是具体产品继承的父类(基类),一般不会独立写
(4)具体产品对象【xxx.cpp】(NiKeShoes\AdidasShoes\LiNingShoes)
具体工厂所创建的对象,就是此类
.
.
(2)工厂方法模式的特点
(1)【有两个类】工厂方法模式抽象出了工厂类,提供创建具体产品的接口,交由子类去实现。
(2)【有两个实现】工厂方法模式的应用并不只是为了封装具体产品对象的创建,而是要把具体产品对象的创建放到具体工厂类实现
.
.
(3)工厂方法模式的缺陷
每新增一个产品,就需要增加一个对应的产品的具体工厂类。相比简单工厂模式而言,工厂方法模式需要更多的类定义。【防盗标记–盒子君hzj】
一条生产线只能一个产品
.
.
(4)工厂方法模式的代码实现
(1)抽象工厂类(ShoesFactory)
作用:提供了创建具体鞋子产品的纯虚函数。
代码实现
// 总鞋厂
class ShoesFactory
{public:virtual Shoes *CreateShoes() = 0;virtual ~ShoesFactory() {}
};
(2)具体工厂类(NiKeProducer、AdidasProducer、LiNingProducer)
作用:继承抽象工厂类,实现对应具体鞋子产品对象的创建
代码实现
// 耐克生产者/生产链
class NiKeProducer : public ShoesFactory
{public:Shoes *CreateShoes(){return new NiKeShoes();}
};// 阿迪达斯生产者/生产链
class AdidasProducer : public ShoesFactory
{public:Shoes *CreateShoes(){return new AdidasShoes();}
};// 李宁生产者/生产链
class LiNingProducer : public ShoesFactory
{public:Shoes *CreateShoes(){return new LiNingShoes();}
};
(3)具体产品对象【xxx.cpp】(NiKeShoes\AdidasShoes\LiNingShoes)【调用阶段了】
作用:main函数针对每种类型的鞋子,构造了每种类型的生产线,再由每个生产线生产出对应的鞋子。需注意的是具体工厂对象和具体产品对象,用完了需要通过delete释放资源【防盗标记–盒子君hzj】
代码实现
int main()
{// ================ 生产耐克流程 ==================== //// 鞋厂开设耐克生产线ShoesFactory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克球鞋广告喊起nikeShoes->Show();// 释放资源delete nikeShoes;delete niKeProducer;// ================ 生产阿迪达斯流程 ==================== //// 鞋厂开设阿迪达斯生产者ShoesFactory *adidasProducer = new AdidasProducer();// 阿迪达斯生产线产出球鞋Shoes *adidasShoes = adidasProducer->CreateShoes();// 阿迪达斯球鞋广喊起adidasShoes->Show();// 释放资源delete adidasShoes;delete adidasProducer;return 0;
}
.
.
3.抽象工厂模式(泛化做)
(0)使用场景
鞋厂为了扩大了业务,不仅只生产鞋子,把运动品牌的衣服也一起生产了
(1)抽象工厂模式的结构组成(和工厂方法模式一样,只是成员更多了)
(1)抽象工厂类厂(Factory)
工厂方法模式的核心类,提供创建具体产品的接口,由具体工厂类实现。
(2)具体工厂类(NiKeProducer)
继承于抽象工厂,实现创建对应具体产品对象的方式。【防盗标记–盒子君hzj】
(3)抽象产品类(Shoes\Clothe)
它是具体产品继承的父类(基类)。
(4)具体产品类(NiKeShoes\NiKeClothe)
具体工厂所创建的对象,就是此类
.
.
(2)抽象工厂模式的特点
提供一个接口,可以创建多个产品族中的产品对象。如创建耐克工厂,则可以创建耐克鞋子产品、衣服产品、裤子产品等
.
.
(3)抽象工厂模式的缺陷
同工厂方法模式一样,新增产品时,都需要增加一个对应的产品的具体工厂类。
.
.
(4)抽象工厂模式的代码
图例
(1)抽象工厂类厂(Factory)
代码实现
// 总厂
class Factory
{public:virtual Shoes *CreateShoes() = 0;virtual Clothe *CreateClothe() = 0;virtual ~Factory() {}
};
(2)具体工厂类(NiKeProducer)
代码实现
// 耐克生产者/生产链
class NiKeProducer : public Factory
{public:Shoes *CreateShoes(){return new NiKeShoes();}Clothe *CreateClothe(){return new NiKeClothe();}
};
(3)抽象产品类(Shoes\Clothe)
代码实现【防盗标记–盒子君hzj】
// 基类 衣服
class Clothe
{public:virtual void Show() = 0;virtual ~Clothe() {}
};// 基类 鞋子
class Shoes
{public:virtual void Show() = 0;virtual ~Shoes() {}
};
(4)具体产品类(NiKeShoes\NiKeClothe)
代码实现【防盗标记–盒子君hzj】
// 耐克衣服
class NiKeClothe : public Clothe
{public:void Show(){std::cout << "我是耐克衣服,时尚我最在行!" << std::endl;}
};// 耐克鞋子
class NiKeShoes : public Shoes
{public:void Show(){std::cout << "我是耐克球鞋,让你酷起来!" << std::endl;}
};
main函数调用
构造耐克工厂对象,通过耐克工厂对象再创建耐克产品族的衣服和鞋子对象。同样,对象不再使用时,需要手动释放资源。
代码实现
int main()
{// ================ 生产耐克流程 ==================== //// 鞋厂开设耐克生产线Factory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克生产线产出衣服Clothe *nikeClothe = niKeProducer->CreateClothe();// 耐克球鞋广告喊起nikeShoes->Show();// 耐克衣服广告喊起nikeClothe->Show();// 释放资源delete nikeShoes;delete nikeClothe;delete niKeProducer;return 0;
}
.
4.模板工厂(大一统)
(1)原理
针对工厂方法模式封装成模板工厂类
.
.
(2)优缺点
主要是将工厂类的封装性提高,达到新增产品时,也不需要修改工厂类,不需要新增具体的工厂类。封装性高的工厂类特点是扩展性高、复用性也高
将工厂方法模式改良成模板工厂,虽然可以解决产品新增时,不需要新增具体工厂类,但是缺少一个可以随时随地获取产品对象的方式,说明还有改进的空间
.
.
(3)模板工厂的代码实现
图例
(1)抽象模板工厂类(AbstractFactory)
其中模板参数:AbstractProduct_t 产品抽象类,如Shoes、Clothe
代码实现
// 抽象模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类
template <class AbstractProduct_t>
class AbstractFactory
{public:virtual AbstractProduct_t *CreateProduct() = 0;virtual ~AbstractFactory() {}
};
.
.
(2)具体模板工厂类(ConcreteFactory)
其中模板参数:AbstractProduct_t 产品抽象类(如Shoes、Clothe)
代码实现
// 具体模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类,ConcreteProduct_t 产品具体类
template <class AbstractProduct_t, class ConcreteProduct_t>
class ConcreteFactory : public AbstractFactory<AbstractProduct_t>
{public:AbstractProduct_t *CreateProduct(){return new ConcreteProduct_t();}
};
.
.
(3)抽象产品类(Shoes\Clothe)
代码实现
// 基类 鞋子
class Shoes
{public:virtual void Show() = 0;virtual ~Shoes() {}
};// 基类 衣服
class Clothe
{public:virtual void Show() = 0;virtual ~Clothe() {}
};
.
.
(4)具体产品类(NiKeShoes\NiKeClothe)
代码实现
// 耐克鞋子
class NiKeShoes : public Shoes
{public:void Show(){std::cout << "我是耐克球鞋,我的广告语:Just do it" << std::endl;}
};// 优衣库衣服
class UniqloClothe : public Clothe
{public:void Show(){std::cout << "我是优衣库衣服,我的广告语:I am Uniqlo" << std::endl;}
};
.
.
main函数调用
根据不同类型的产品,构造对应的产品的工厂对象,便可通过对应产品的工厂对象创建具体的产品对象
代码实现
int main()
{// 构造耐克鞋的工厂对象ConcreteFactory<Shoes, NiKeShoes> nikeFactory;// 创建耐克鞋对象Shoes *pNiKeShoes = nikeFactory.CreateProduct();// 打印耐克鞋广告语pNiKeShoes->Show();// 构造优衣库衣服的工厂对象ConcreteFactory<Clothe, UniqloClothe> uniqloFactory;// 创建优衣库衣服对象Clothe *pUniqloClothe = uniqloFactory.CreateProduct();// 打印优衣库广告语pUniqloClothe->Show();// 释放资源delete pNiKeShoes;pNiKeShoes = NULL;delete pUniqloClothe;pUniqloClothe = NULL;return 0;
}
.
.
5.产品注册模板类+单例工厂模板类(超大型工程->一般用不到了)
总结
工厂模式是一种思想
理解工厂模式的时候最好配合现有的工程进行理解,【防盗标记–盒子君hzj】公司的工程也好,demo的工程也好,理解才会更深刻
一般基类都是写在.h文件,实例化都是写在.cpp文件【防盗标记–盒子君hzj】
工厂模式优点
(1)创建对象时不会对客户端暴露创建逻辑
(2)通过使用一个共同的接口来指向新创建的对象
参考资料
https://www.cnblogs.com/xiaolincoding/p/11524376.html
https://www.cnblogs.com/xiaolincoding/p/11524401.html
.
.
第四层:代码重构
1、重构的定义
对系统和软件内部结构的一种调整,提高其可理解性,降低其修改成本。
.
.
2、重构的目的
(1)增加代码的兼容性、拓展性
(2)增加代码的逻辑性,减少复用及多余的流程,逻辑清晰明了
在整理的时候把架构的枝叶都剪掉或者功能合并,反正重构后代码更整洁了
(3)提高代码开发的效率
理解程序有两面价值
“今天你可以为代码做什么”–【治标】实现暂时的功能
“明天代码可以为你做什么”–【治本】做好技术和程序储备,框架扩展性更强,更快的实现以后能做什么
代码都是需要进行维护与的,适当的时候进行重构,开发起来阻力才小,效率才高,差的系统代码,到处是补丁,写一个bug要修三个bug【防盗标记–盒子君hzj】
在做某个功能需求时,需要花掉大量的时间,才能找到和需求有关联的代码,大家可能深有体会,一个系统在上线初期,向系统中增加功能时,完成速度非常快,但是如果不注重代码质量,后期向系统中添加一个很小的功能可能就需要花上一周或更长的时间【防盗标记–盒子君hzj】
(1)【模块性的】系统架构能更快的响应需求,维护好扩展性更强的代码架构
(2)【代码性的】增加可维护性,降低维护成本,代码可读性和编辑性更强,对团队和个人都是正向的良性循环
(4)提高代码的可读性
我们是代码的作者,后人是代码的读者。写代码要时刻审视,做前人栽树后人乘凉、不做前人挖坑后人陪葬的事情
当你面对结构混乱、毫无章法的代码结构,词不达意的变量名、方法名时,根本无法读下去
(5)【个人的】通过复盘,更好的积累自己的技术和代码储备
(6)最终,把demo实验验证上升到一个产品
.
.
3、重构的挑战(难处)
1.意识问题
如果我纯粹为今天工作,明天我将完全无法工作,但其实大多数人都是不喜欢重构工作的,就像没有人愿意给别人“擦屁股”一样【防盗标记–盒子君hzj】
.
2.方法论问题
不知道怎么重构,缺乏重构的经验和方法论
.
3.额外工作问题
重构需要你付出额外的工作去读懂别人的程序【学习还行】,重构会破坏现有程序,带来意想不到的Bug,不想去承受这些意料之外的Bug。【防盗标记–盒子君hzj】
.
4.利益问题
很难看到短期收益,无法产出新的功能,业务上没有任何推进,长远看来,说不定当项目收获这些利益时,你已经不负责这块工作了
.
.
4、重构的程度及方法论【重点】
1.小型重构方法论【日常工作、空闲时间】
(1)变量规范命名(针对词不达意的变量再命名)
(2)逻辑过于复杂,如if-else过多
(3)消除超大函数
一个好的函数必须满足单一职责原则,短小精悍,只做一件事
(4)整理类关系
多组合,少继承
(5)消除重复代码
法将它们合而为一,避免重复阅读
(6)消除无用代码(不要舍不得)
一种是没有使用场景,如果这类代码不是工具方法或工具类,而是一些无用的业务代码,那么就需要及时的删除清理。
另外一种是用注释符包裹的代码块,这些代码在被打上注释符号的时候就应该被删除【防盗标记–盒子君hzj】
(7)整理代码注释
无病呻吟,代码本身一眼就能看明白是在干什么,写代码的人非要在这个地方加一个不关痛痒的注释,这个注释完全是口水话,毫无价值可言
不合理的注释
注释是一把双刃剑,好的注释能够给我们好的指导,不好的注释只会将人误导
.
.
2.大型重构方法论【制定计划,按部就班】
对代码顶层架构进行重新设计,这类重构可能需要进行原则再定义、模式再定义甚至业务再定义
(1)系统业务结构重构
(2)技术模块结构
(3)代码数据流结构
.
.
3.重构技巧
(1)工作时自下到上,重构时从上至下,由外到内进行建模分析,理清各种关系,是重构的重中之重
(2)提炼类,复用函数,下沉核心能力,让模块职责清晰明了【防盗标记–盒子君hzj】
(3)依赖接口优于依赖抽象,依赖抽象优于依赖实现,类关系能用组合就不要继承
(4)类、接口、抽象接口设计时考虑范围限定符,哪些可以重写、哪些不能重写,泛型限定是否准确
.
.
4.重构的效果到适度就好(定重构的需求)
(1)重构太细致泛化能力差,容易出现无病呻吟的问题
(2)重构力度太小,效果不明显【防盗标记–盒子君hzj】
(3)重构到可以了解清楚方向和得到方法论就好
.
.
5、避免经常重构的方法
(1)【规范标准】遵循代码开发规范、准则和格式对齐
正所谓理论到头成书籍,工程到头成规范
.
.
(2)【架构师】有一个好的架构师,框架要求不要变来变去【防盗标记–盒子君hzj】
当然可是可遇不可求的,分分钟产品经理觉得这个不重要哈哈,反正开发的不是他,当然大部分同事都是能达成共识的
.
.
6、大型代码重构流程【按部就班,一步一步推进】
1.事前【目标计划】
(1)自己梳理现有架构
对涉及重构部分的现有业务、架构进行梳理,明确重构的内容在系统的哪个服务层级、属于哪个业务模块,依赖方和被依赖方有哪些,有哪些业务场景,每个场景的数据输入输出是怎样的
(2)会议明确重构的内容、方向目标
满足未来的方向,且方向是经得起大家的质疑
(3)会议宣讲,项目立项
重构涉及到哪些业务和场景、大概的重构方式、业务影响可能有哪些,难点及可能在哪些步骤出现瓶颈
周知大概的时间计划表(粗略的大致时间)【防盗标记–盒子君hzj】
明确各组主要负责的人
2.事中
(1)会议架构设计与评审
(1)明确业务架构(图)
(2)明确系统技术模块架构(图)
(3)明确源码数据流程架构(图)
(2)分工进行代码的编写、测试、线下实施
3.事后【沉淀形成收获】
【公开】开一个重构复盘会
(1)检查每个参与方是否都达到了重构要求的标准【防盗标记–盒子君hzj】
(2)复盘重构期间遇到的问题、以及解决方案是什么样的
(3)沉淀方法论避免后续的工作出现类似的问题。
.
【个人】沉淀项目部署经验
(1)业务架构
(2)系统技术模块架构【依赖?】
(3)源码输入输出数据流架构
(4)相关的设计图和文档
参考资料
https://mp.weixin.qq.com/s/5Qxll-uxj4eCAl7pQ2PAwg
https://blog.csdn.net/Travis_X/article/details/87968746
还有经常看的那基本代码整洁之道、代码重构的几本书
【编码经验】数据结构与语法规范、计算机算法、架构模式设计、代码重构相关推荐
- 计算机算法课程论文设计与实现,算法设计与分析课程论文
算法设计与分析课程论文 "卓越工程师教育培养计划"(简称卓越计划)旨在培养一批创新能力强.适应经济社会发展需要的高质量工程技术人才.在南通大学计算机科学与技术学院制定的软件工程专业 ...
- 1.2 User Interface 规范(系统开发架构与设计步步谈)
系统开发管理.架构与设计步步谈随笔索引 前言导读 网站框架系列目录 1.1 编码规范 1.2 User Interface 规范 1.3 菜单管理 1.4 数据验证 1.5 异常处理机制 1.6 安 ...
- 架构业务wait和sleep区别狭义jiavaBean规范,三层架构模式
在写这篇文章之前,xxx已经写过了几篇关于改架构业务主题的文章,想要了解的朋友可以去翻一下之前的文章 每日一道理 聪明人学习,像搏击长空的雄鹰,仰视一望无际的大地:愚笨的人学习,漫无目标,犹如乱飞乱撞 ...
- 计算机算法对程序设计的作用,计算机编程中数学算法的优化策略
李钰 摘要:在计算机编程中,合理地运用数学算法所拥有的优势不但可以完好地针对所拥有的问题进行总结分类归纳,还可以将其归纳作为基础从而进行针对性的统一计算,并且能够将非常复杂的问题进行整体的简化并且将其 ...
- SM2算法的加密签名消息语法规范(二)如何构造
既然是要构造国密规范的消息语法数据类型,那当然要用到gmssl库咯.(GmSSL项目是OpenSSL项目的分支,并与OpenSSL保持接口兼容.因此GmSSL可以替代应用中的OpenSSL组件,并使应 ...
- Bootstrap HTML编码语法规范
语法规范 HTML编码的基本语法格式: 用两个空格来代替制表符(tab) -- 这是唯一能保证在所有环境下获得一致表现的方法. 嵌套元素应当缩进一次(即两个空格). 对于属性的定义,确保全部使用双引号 ...
- 北京交通大学计算机考研906计算机专业基础初试经验分享北交大考研计算机
[2023考研重要时间节点] 2022年9月底预报名,10月报名. 2022年12月底参加初试. 2023年2月中下旬公布初试成绩. 2023年3月10号左右公布国家线. 2023年3月20号左右计算 ...
- [GO语言基础] 二.编译运行、语法规范、注释转义及API标准库知识普及
作为网络安全初学者,会遇到采用Go语言开发的恶意样本.因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识:另一方面是分享与读者,希望大家一起进步.前文介绍了什么是GO语言 ...
- 计算机算法常用术语中英对照
1 第一部分.计算机算法常用术语中英对照2 Data Structures 基本数据结构3 Dictionaries 字典4 Priority Queues 堆5 Graph Data Structu ...
最新文章
- 清华大佬手把手教你使用Python进行数据分析和可视化
- iOS如何使用三方字体
- webpack联邦模块之webpack运行时
- Linux crond 每分钟,每小时,每天,每周,每月,每年 的表达式写法
- Android 5.0新特性
- 设计模式之三:观察者模式
- 20190814 On Java8 第四章 运算符
- 牛客——数据库实战(1~30)
- 当BeanUtils遇到泛型
- 谷歌浏览器如何截取长屏幕(全屏截图)
- 关于海康相机ip地址无法更改问题
- C语言入门教程(一)
- 服务器主板显示ba,为什么我的设计器不能显示,但是可以调通,主板信息读出发生错误...
- win10网络连接出现感叹号
- 一些无线通信系统模型的概念
- [C++教程①]--了解c语言以及第一行代码
- Python飞机大战源代码
- 2021 HZNU Winter Camp -- LCA
- 制作启动盘及安装系统
- mysql密码@_如何重设MySQL密码