《重构改善代码设计》

news2024/11/24 14:11:57

文章目录

    • 1.重构的原则
    • 2.代码的坏味道
    • 3.第一组重构
      • 3.1.提炼函数
      • 3.2.内联函数
      • 3.3.提炼变量
      • 3.4.内联变量
      • 3.5.修改函数名称
      • 3.6.封装变量
      • 3.7.变量改名
      • 3.8.引入参数对象
      • 3.9.函数组合成类
      • 3.10.函数组合成变换
      • 3.11.拆分阶段
    • 4. 封装
      • 4.1. 封装记录
      • 4.2. 封装集合
      • 4.3. 以对象取代基本类型
      • 4.4. 以查询取代临时变量
      • 4.5. 提炼类
      • 4.6. 内联类
      • 4.7. 隐藏委托关系
      • 4.8. 移除中间人
      • 4.9. 替换算法
    • 5. 搬移特性
      • 5.1. 搬移函数
      • 5.2. 搬移字段
      • 5.3. 搬移语句到函数
      • 5.4. 搬移语句到调用者
      • 5.5. 以函数调用取代内联代码
      • 5.6. 移动语句
      • 5.7. 拆分循环
      • 5.8. 移除死代码
    • 6. 重新组织数据
      • 6.1. 拆分变量
      • 6.2. 字段改名
      • 6.3. 以查询取代派生变量
      • 6.4. 将引用对象改为值对象
      • 6.5. 将值对象改为引用对象
    • 7. 简化条件逻辑
      • 7.1. 分解条件表达式
      • 7.2. 合并条件表达式
      • 7.3. 以卫语句取代嵌套条件表达式
      • 7.4. 以多态取代条件表达式
      • 7.5. 引入特例
      • 7.6. 引入断言
    • 8. 重构API
      • 8.1. 查询函数和修改函数分离
      • 8.2. 函数参数化
      • 8.3. 移除标记参数
      • 8.4. 保持对象完整
      • 8.5. 以查询取代参数
      • 8.6. 以参数取代查询
      • 8.7. 移除设值函数
      • 8.8. 以工厂函数取代构造函数
      • 8.9. 以命令取代函数
      • 8.10. 以函数取代命令
    • 9. 处理继承关系
      • 9.1. 函数上移
      • 9.2. 字段上移
      • 9.3. 构造函数本体上移
      • 9.4. 函数下移
      • 9.5. 字段下移
      • 9.6. 以子类取代类型码
      • 9.7. 移除子类
      • 9.8. 提炼基类
      • 9.9. 折叠继承体系
      • 9.10. 以委托取代子类
      • 9.11. 以委托取代基类

1.重构的原则

在这里插入图片描述

2.代码的坏味道

  1. 神秘命名:不能清晰表述含义和功能的命名。
    重构手法:改变函数命名、变量改名、字段改名
  2. 重复代码:一个以上地方出现相同的代码结构
    重构手法: 提炼函数,移动语句、函数上移
  3. 函数过长:需要注释来解释的时候,就要考虑是否需要拆解函数;条件表达式也是拆解函数的信号。
    重构手法:提炼函数、以查询取代临时变量、引入参数对象、保持对象完整、以命令取代函数、分解条件表达式、以多态取代条件表达式、拆分循环。
  4. 参数列表过长:从一个现有的数据结构中获取多个值时,可以直接使用数据结构;多个函数有同样的参数,可以将函数组合成类,把函数参数作为类的属性。
    重构手法:以查询取代参数、保持对象完整、引入参数对象、移除标记参数、函数组合成类。
  5. 全局数据:可以在代码的任何位置进行修改,带来隐患。
    重构手法:封装变量
  6. 可变数据:随着变量作用域的扩展,会使风险变大
    重构手法:封装变量、拆封变量、移动语句、提炼函数、将查询函数和修改函数分离、移除设值函数、以查询取代派生变量、函数组合成类、函数组合成变换、将引用对象改为值对象。
  7. 发散式变换:如果某个模块经常因为不同的原因在不同的方向上发生变换;增加某个功能,需要修改很多处。
    重构手法:提炼函数、搬移函数、提炼类、拆分阶段。
  8. 散弹式修改:每遇到某种变化,需要在许多不同的类内做许多小的修改
    重构手法:搬移函数、搬移字段、函数组合成类、函数组合成变换、拆分阶段、内联函数、内联类。
  9. 依恋情结:模块化即是力求将代码分出区域,最大化区域内部交互,最小化跨区域交互;一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流;将总是一起变化的东西放在一块儿。
    重构手法:搬移函数、提炼函数
  10. 数据泥团: 多个类中相同的字段,函数中相同参数,绑定在一起出现的参数。
    重构手法:提炼类、引入参数对象、保持对象完整
  11. 基本类型偏执:不要过分依赖基础类型,合适的时候封装为对象。
    重构手法:以对象取代基本类型、以子类取代类型码、以多态取代条件表达式、提炼类、引入参数对象
  12. 重复的switch:强调重复的switch,维护时必须找到所有的switch语句。
    重构手法:以多态取代条件表达式
  13. 用管道取代循环语句
  14. 冗赘的元素:过于简单的结构、类或函数可以直接内联掉不需要在单独封装
    重构手法:内联函数、内联类、折叠继承体系
  15. 过度设计:不要为了未来的一些事情而使当前的代码进行特殊处理
    重构手法:折叠继承体系、内联函数、内联类、改变函数声明、移除死代码
  16. 临时变量:类内部某个字段仅为某特定情况而设置
    重构手法:提炼类、搬移函数、引入特例
  17. 过长的消息链:跨多层对象取值即是过长的消息链。
    重构手法:隐藏委托关系、提炼函数、搬移函数
  18. 过度委托:将一个类的大部分函数都委托给其他类,此时可移除中间类
    重构手法:移除中间人、内联函数、以委托取代基类、以委托取代子类
  19. 内幕交易:模块间进行大量的数据交互,应尽量减少。
    重构手法:搬移函数、搬移字段、隐藏委托关系、以委托取代基类、以委托取代基类
  20. 类过大:不要利用一个类做过多的事情,要遵循单一职责
    重构手法:提炼类、提炼基类、以子类取代类型码
  21. 异曲同工类:接口不一致的类无法互相替换。
    重构手法:改变函数声明、搬移函数、提炼基类
  22. 纯数据类:纯数据类即拥有一些字段,以及用于访问字段的函数,避免出现public字段。
    重构手法:封装记录、移除设值函数、搬移函数、提炼函数、拆分阶段
  23. 被拒绝的遗赠:基类中不应该有子类不想或不应该继承的数据和函数。
    重构手法:函数下移、字段下移、以委托取代子类、以委托取代基类
  24. 注释:当出现需要注释解释的代码,则表示需要通过提炼函数来解释其行为。
    重构手法:提炼函数、搬移函数、引入断言

3.第一组重构

3.1.提炼函数

目的: 如果一段代码需要浏览全部代码才能知道它的功能,那么就应该提炼函数,以函数实现功能进行命名。
实现:
a.创造新函数并以功能进行命名,以做什么来命名,而不是怎么做来命名.
b.将待提炼代码拷贝到新函数中,并检查是否引用了作用域以外的函数或变量,如果有则以参数方式传入新函数。
c.在源函数中替换为新函数。

3.2.内联函数

目的:
a.内部函数代码和函数名一样清晰易读,就没有必要封装为函数
b.多个组织不合理的函数,可以将其合并为大函数再重新提炼。
实现:
a.检查函数,确定其不具备多态
b.找出函数所有调用点,替换为函数本体
c.删除函数定义

3.3.提炼变量

目的: 表达式复杂难读时,局部变量可以帮助将表达式分解为比较容易管理的形式
实现:
a.确定要提炼的表达式没有副作用
b.声明不可变变量,复制要提炼的表达式使其值赋值给新变量
c.用新变量取代表达式

3.4.内联变量

目的: 表达式比变量表达意思更清晰时,内联变量
实现:
a.确定变量右侧表达式没有副作用
b.将使用变量的地方替换为表达式
c.替换所所有使用变量的地方
d.删除变量的声明和赋值语句

3.5.修改函数名称

目的: 好的函数名,可以直接通过函数名看出函数的功能,不需要查看代码的实现
实现:
简单实现:
a.想要移除参数,需要确认函数体内没有使用该参数
b.修改函数名字
c.找到所有旧函数将其替换
迁移式实现:
a.有必要的化,先重构函数内部,方便后续步骤展开
b.使用提炼函数,将函数提炼成新函数
c.如果需要添加参数,采用简单做法添加
d.对旧函数使用内联函数

3.6.封装变量

目的:
a.提供一个清晰的观测点,由此监控数据的变化和使用
b.可以轻松添加数据被修改时的验证和后续逻辑
c.缩小变量的可见范围
实现:
a.创建封装函数,在其中访问和更新变量值
b.修改所有使用该变量的地方,使其调用封装函数
c.限制变量的可见性

3.7.变量改名

目的: 好的变量名可以解释一段程序在做什么
实现:
a.如果变量被广泛使用,考虑运用封装变量将其封装
b.修改所有使用该变量的地方

3.8.引入参数对象

目的: 一组数据结伴同行,多处被使用,可将其封装为数据结构
实现:
a.如果没有合适的数据结构,就创建一个
b.使用修改函数名称给原函数新增一个新建的数据结构作为参数
c.修改所有的调用处,使用新的数据结构
d.用数据结构中的元素,替换参数列表中的参数项,删除原来的参数

3.9.函数组合成类

目的: 一组函数形影不离的操作同一块数据,通常是将这块数据作为参数传递给函数
实现:
a.运用封装基类对多个函数公用的数据基类加以封装
b.对于使用该基类结构的每个函数,运用搬移函数将其移新类
c.用以处理该数据记录的逻辑可以用提炼函数提炼出来,并移入新类

3.10.函数组合成变换

目的: 有多个地方对关联的一组数据进行计算处理,可以将其合并到一起共同处理。以减少对关联数据做相似的处理,来减少代码重复。
实现:
a.创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值
b.挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录中,修改客户端代码,令其使用新字段
c.针对其他的相关计算逻辑,重复上述步骤

3.11.拆分阶段

目的: 一段代码在处理超过一件不同的事情时,需要拆分
实现:
a.将第二阶段的代码提炼成独立的函数
b.引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中
c.检查第二阶段的参数,如果被第一阶段用到,将其移入中转数据结构中
d.对第一阶段的代码运用提炼函数,让提炼出的函数返回中转数据结构

4. 封装

4.1. 封装记录

目的: 封装可变数据,可以隐藏存储的细节和计算的过程
实现:
a.对持有记录的变量使用封装变量,将其封装到一个函数中
b.创建类,记录封装起来,将记录变量的值替换为类的实例,在类上定义访问接口,用于返回原始记录。修改封装变量的函数,令其使用这个访问函数
c.新建函数,让它返回该类的对象,而非原始的记录
d.使用新的访问函数,替换原来返回记录的函数。
e.删除类的原始记录的访问函数

4.2. 封装集合

目的: 封装可变数据,可以清楚的看到数据被修改的地方和修改方式,后续可以方便的修改数据结构。
实现:
a.如果集合的引用尚未被封装起来,先用封装变量封装它
b.在类上添加用于添加和删除集合元素的函数
c.查找所有引用点,如果调用方直接修改集合,令该处调用使用新的添加/移除元素函数。
d.修改集合中的取值函数,使其返回一个副本

4.3. 以对象取代基本类型

目的: 当基础类型变量的使用不再简单时封装为对象
实现:
a.如果变量尚未被封装起来,先使用封装变量封装它
b.创建一个类,类的构造函数保持这个数据值,并提供一个取值函数
c.修改设值函数,令其创建一个新类的对象并将其存入字段,如果有必要的话,同时修改字段的类型声明
d.修改取值函数,使其调用新类的取值函数,并返回结果
e.修改明确清楚的函数名字,将引用对象改为值对象或将值对象改为引用对象,用以明确新对象时值对象函数引用对象

4.4. 以查询取代临时变量

目的: 可以避免在多个函数中编写相同的逻辑
实现:
a.检查变量在使用前是否已经计算完毕,检查计算它的代码是否能得到相同的值
b.可将变量修改为只读
c.将变量赋值代码提炼为函数
d.应用内联变量手法移除临时变量

4.5. 提炼类

目的:
a.一个类应该是一个清晰的抽象的,只处理一些明确的责任。
b.当一个类不断新增功能,修改等变得复杂时,就要考虑将其功能进行分离。
实现:
a.决定如何分解类所负的责任
b.创建新类,用以表现从旧类中分离出来的责任。
c.构造旧类时,创建一个新类,建立旧类到新类的访问接口
d.将分离功能相关的字段和函数搬移到新类
e.去除不需要的接口

4.6. 内联类

目的: 如果一个类不再承担足够责任,不再有单独存在的理由
实现:
a.待内联类的public接口在目标类中创建对应函数
b.修改源类public接口的所有引用点,使其调用目标类的接口
c.将待内联类中的函数和数据搬移到目标类
d.删除待内联类

4.7. 隐藏委托关系

目的: 不要使用委托类去获取被委托类的实例,然后调用其接口;如果被委托类的接口改变,那么所有客户端都需要修改
实现:
a.对于每个委托关系中的函数,在委托类端创建一个简单的委托函数
b.调整客户端使其调用委托类的函数
c.如果将来不再有客户端使用委托类,便可以移除委托对象中的函数

4.8. 移除中间人

目的: 如果委托类的特性变多,那么相应的转发函数就会变多,此时可以考虑移除转发函数
实现:
a.为委托对象创建一个取值函数
b.对于每个委托函数,让其客户端转为连续的访问函数调用

4.9. 替换算法

目的: 用比较清晰的方式取代复杂的方式
实现:
a.整理待替换算法,保证它被提取到一个独立的函数中
b.进行独立函数测试
c.编写新算法
d.测试新旧算法,如果运行结果相同,则可替换新算法

5. 搬移特性

5.1. 搬移函数

目的:
a.提高代码的模块化
b.将关联紧密的上下文整合到一起
实现:
a.检查函数在当前上下文里引用的所有程序元素,考虑是否将它们一并搬移
b.检查待搬移函数是否具备多态性
c.将函数赋值一份到目标上下文中
d.从源上下文中正确引用目标函数
e.修改源函数使其成为一个委托函数
f.对源函数使用内联函数,去除委托

5.2. 搬移字段

目的:
a.如果修改一个数据结构时,总是需要同时修改另一条数据结构,那么说明字段放错了位置
b.如果更新一个字段,需要同时在多个结构中做修改,也是一个需要搬移的征兆
实现:
a.确保源字段已经得到了良好的封装
b.在目标对象上创建一个字段
c.确保源对象里能正常引用目标对象
d.调整源对象的访问函数,令其使用各目标对象的字段
e.移除源对象上的字段

5.3. 搬移语句到函数

目的: 如果调用某个函数时,总有一些相同的代码也需要每次执行,那么需要考虑将代码合并到函数里
实现:
a.将重复代码段移动到紧邻目标函数位置
b.如果目标函数只有一个调用点,那么将重复代码段移入目标函数中
c.不止一个调用点,运用提炼函数,将目标函数共同提炼成新函数
d.所有调用点替换为新函数
e.运用内联函数,将目标函数内联到新函数中

5.4. 搬移语句到调用者

目的: 函数边界发生偏移,即以往多个地方共用的行为,现在会出现不同的行为时,需要将不同的部分移除出函数,搬移到调用者
实现:
a.如果待搬移的语句仅有一处调用,那么直接搬移到调用函数中
b.如果有待搬移的语句有多处调用,那么需要先提炼函数,再搬移到调用函数中

5.5. 以函数调用取代内联代码

目的:
a.消除重复代码
b.如果内联代码时对已有函数的重复,此时使用已有函数替换内联代码
实现: 将内联代码替换为对一个既有函数的调用

5.6. 移动语句

目的: 让存在管理的东西一起出现,可以使代码更容易理解
实现:
a.确定待移动的代码片段应该搬往何处,检查待搬移代码段与目标之间是否会有影响
b.剪切待搬移代码段到目标位置

5.7. 拆分循环

目的: 为了减小多次循环次数,一个循环内做多件事情;那么修改循环时,就需要去理解两件事情
实现:
a.复制一遍循环代码
b.识别并删除循环中的重复代码,使每个循环只做一件事情

5.8. 移除死代码

目的:
a.阅读代码、理解软件运作原来时,无用代码确实会带来额外的思想负担。
b.没有用到的代码就删除掉,就算以后有可能使用到,在真正使用到的时候可通过版本控制来重新添加
实现:
a.查看死代码时候还有地方在调用
b.删除死代码

6. 重新组织数据

6.1. 拆分变量

目的: 临时变量被多次赋值时就需要拆分
实现:
a.在待分解变量的声明及第一次被赋值处,修改其名称
b.如果可以将新的变量声明为不可修改
c.以该变量第二次被赋值处为界,使其使用新变量
d.重复以上步骤,修改其他地方的赋值

6.2. 字段改名

目的: 数据结构中的字段,对于阅读者理解特别重要
实现:
a.如果作用域很小,则直接替换后测试
b.如果记录还没有封装,则先封装记录
c.在对象内部对私有字段改名,并调整内部访问该字段的函数
d.如果构造函数使用了旧的字段名,则使用修改函数名字将其改名
e.用于函数改名给访问函数改名

6.3. 以查询取代派生变量

目的:
a.可变数据是软件中最大的错误源头之一
b.在一处修改数据,可能在另一处造成难以发现的破坏
c.去除可变数据不现实,但尽量把可变数据的作用域限制在最小范围
实现:
a.找出所有对变量做更新的地方。如果有必要用拆分变量分割各个更新点
b.新建函数用于计算该变量的值
c.用引入断言,断言该变量和计算函数始终给出同样的值
d.修改读取该变量的代码,令其调用新建的函数
e.用移除死代码去掉变量的声明和赋值

6.4. 将引用对象改为值对象

目的:
a.如果为引用对象那么想要更新属性,则需要保留原对象不动,只更新属性
b.如果为值对象那么想要更新属性,可以直接替换整个内部对象
实现:
a.检查重构目标是否为不可变对象,或者是否可修改为不可变对象
b.用移除设值函数逐一去掉所有设值函数
c.提供过一个基于值的相等性判断函数,在其中使用值对象的字段

6.5. 将值对象改为引用对象

目的:
a.如果一个数据中可能包含多个记录,而这些记录都管理在同一个逻辑数据结构中时,即可使用引用对象,去除重复的逻辑数据结构。
b.去除重复的数据结构,可以降低内存的使用情况
c.对于修改来说,数据结构的副本在修改时需要找到所有的副本进行修改
实现:
a.为相关的对象创建一个仓库
b.确保构造函数有办法找到关联对象的正确实例
c.修改宿主对象的构造函数,令其从仓库中获取关联对象

7. 简化条件逻辑

7.1. 分解条件表达式

目的: 降低条件表达式的复杂度,减少当条件较多时函数的规模,提升代码可读性。
实现: 对条件判断和每个条件分支分别运用提炼函数手法

7.2. 合并条件表达式

目的:
a.检查条件不相同但最终行为却一致
b.如果认为检查项彼此独立,不能被视为同一次检查,那就不能合并
实现:
a.确定表达式没有副作用
b.使用适当的逻辑运算,将表达式合并
c.考虑对合并的表达式提炼为函数

7.3. 以卫语句取代嵌套条件表达式

目的: 某个条件为真,则做一些处理后,直接退出函数
实现:
a.选择最外层需要被替换的条件逻辑,替换为卫语句
b.如果多个卫语句引发同样的结果,则使用合并条件表达式合并
场景:
a.两个分支都属于正常行为
b.一个分支为正常行为,一个分支为异常行为

7.4. 以多态取代条件表达式

目的:
a.利用多态承载各个类型特有的行为,以去除重复的分支逻辑
b.有一个基础逻辑,在其之上又有一些差异。可将基础逻辑放进基类,差异点放进子类
实现:
a.如果类不具备多态,使用工厂函数创建并返回实例对象
b.调用方使用工程函数获取实例
c.带有条件逻辑的函数移动到基类中
d.重写基类中的条件逻辑函数,实现有差异的条件逻辑

7.5. 引入特例

目的: 一个数据结构的使用者都在检查某个特殊值,并且当这个特殊值出现时所作的处理都相同。如果有此问题,就要想办法把这个处理逻辑收拢到一起
实现:
a.给重构目标检查特例的属性,使其返回false
b.创建一个特例对象,其中只有检查特性的属性,返回false
c.对特例值比较的代码,提炼函数;使用到的地方使用新函数替换
d.将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生成
e.修改特例函数的主题,函数中使用检查特例的属性
f.使用函数组合成类活函数组合成变换,将特例搬移到特例对象中
g.特例函数内联到使用到的地方

7.6. 引入断言

目的: 只用当某个条件为真时,该段代码才有效
实现: 如果发现代码某个添加始终为真,就加入一个断言

8. 重构API

8.1. 查询函数和修改函数分离

目的: 当查询和修改分离后,在任何地方都可以直接调用,不用操心其他的事情
实现:
a.复制整个函数,并以查询命名
b.从新函数中删除与查询不相关的语句
c.所有调用的地方进行替换
d.原函数中去掉返回值

8.2. 函数参数化

目的: 两个或多个函数相似,自由字面值不同,可使用传入不同的参数,将其合并去除重复代码
实现:
a.从相似的函数中选择一个
b.把需要作为参数的,添加到参数列表中
c.修改所有调用处
d.修改函数体,使其使用新传入的参数
e.对其所有相似函数进行修改

8.3. 移除标记参数

目的:
a.标记参数即函数内根据标记参数做不同的处理逻辑
b.传入的参数影响了函数内部的控制流,这才是标记参数
c.会隐藏掉函数调用中存在的差异
实现:
a.针对参数的每一种可能值,新建一个明确函数
b.使用标记参数的地方,改用新函数

8.4. 保持对象完整

目的:
a.可以更好的应对变化,如果将来需要增加字段,则可以方便扩展
b.可以缩短函数参数列表
实现:
a.新建空函数,给以期望的参数列表
b.新函数中调用旧函数,并把新参数映射到就函数参数列表中
c.替换所有旧函数的调用为新函数
d.将旧函数内联到新函数中
场景: 从一个函数获取几个值,然后有将这几个值传入另一个函数

8.5. 以查询取代参数

目的:
a.避免函数参数重复
b.缩短参数列表
实现:
a.如果有必要,将参数的计算过程到提炼到独立的函数中
b.函数体内引用的地方,修改为新提炼的函数
c.去掉参数
场景: 函数调用时传入一个值,但这个值函数自己可以容易获得

8.6. 以参数取代查询

目的:
a.改变代码的依赖关系
b.减少全局变量的引用
c.不要将所有依赖关系都变成参数,会导致参数列表冗长重复
实现:
a.对查询操作的代码提炼变量,将其从函数体中分离出来
b.对代码提炼函数
c.使用内联变量,消除刚才提炼出来的变量
d.对原来的函数使用内联函数
e.新函数改名为原函数

8.7. 移除设值函数

目的:
a.创建对象后,属性就不会改变的属性,去掉设值函数
b.只在构造函数中调用一次的设值函数
实现:
a.构造函数无法确定具体值时,将参数作为构造函数参数;在构造函数中调用设值函数,对字段设值
b.移除所有除了构造函数之外的设值函数调用,改为新的构造函数,并测试
c.使用内联函数消去设值函数。如果可以把字段设置为不可变类型

8.8. 以工厂函数取代构造函数

目的: 解决无法根据环境或参数信心返回子类的实例或代理对象问题
实现:
a.新建工厂函数,使其调用构造函数
b.将调用构造函数的代码修改为调用工厂函数
c.缩小构造函数的可见范围

8.9. 以命令取代函数

目的:
a.提供更大的灵活性和更强的表达能力
b.命令对象会增加代码复杂性
c.将复杂的函数拆解为多个方法,彼此之间通过成员变量进行数据共享
实现:
a.为需要封装的函数创建空类,并根据函数命名类
b.使用搬移函数将函数移动到类中
c.考虑给每个参数创建一个字段,在构造函数中添加对应的参数

8.10. 以函数取代命令

目的: 相对于命令对象,将处理简单的命令对象,变换为函数
实现:
a.运行提炼函数将命令对象的代码提炼到一个函数中
b.对命令对象中使用的函数进行内联函数方式合并到新函数中
c.将构造函数参数移动到新函数参数列表中
d.移除死代码并删除命令类

9. 处理继承关系

9.1. 函数上移

目的: 避免重复代码
实现:
a.检查待上移函数,确定它们完全一致
b.检查函数体内的函数和属性能在基类中调用到
c.基类中创建函数,并上移子类代码到基类新函数中
d.逐个移除子类函数
场景: 某个函数在各个子类中的函数体都相同

9.2. 字段上移

目的:
a.减少重复的成员变量的声明
b.有可能将使用改字段的行为从子类上移到基类中
实现:
a.针对待上移字段,检查它们的所有使用点,确认它们以同样的方式被使用
b.如果这些字段的名称不同,先使用变量改名为它们取个相同的名字
c.在基类中新建一个字段
d.移除子类中的字段
场景: 子类中出现重复的成员变量

9.3. 构造函数本体上移

目的: 消除构造函数中重复的语句
实现:
a.为基类创建构造函数
b.将子类构造函数中公共语句移动到基类构造函数调用语句之后
c.移除子类中的公用代码段。公共部分使用到的变量,作为基类的参数传给基类构造函数
d.对于无法简单上移到基类的公共代码,先应用提炼函数,再运用函数上移提升

9.4. 函数下移

目的: 如果基类中的某个函数只与一个(或少数几个)子类有关,那么将函数下移到子类中。避免非通用函数出现在基类中。
实现:
a.将基类的函数本体复制到每一个需要此函数的子类中
b.删除基类中的函数
c.将函数移动到每一个需要使用它的子类中

9.5. 字段下移

目的: 如果某个属性只被一个或者一小部分子类用到,那么将其下移到子类中,避免非通用成员变量在基类中。
实现:
a.在所有需要该属性的子类中声明属性
b.将其从基类移除
c.将该属性从所有不需要它的子类中删除

9.6. 以子类取代类型码

目的: 可以使用多态的方式处理条件语句
实现:
a.自封装类型码字段
b.任选一个类型码取值,创建子类,覆写类型码的取值函数,使其返回类型码的字面值
c.创建一个选择器逻辑,把类型码参数映射到新的子类
d.针对每个类型码取值,重复上述步骤
e.使用函数下移和以多态取代条件表达式处理原本访问类型码的函数
f.移除原有类型码的访问函数

9.7. 移除子类

目的: 子类功能变少,或者随着功能的改变被调整,不在需要时,去除子类
实现:
a.使用工厂函数取代构造函数,把子类的构造包装到基类的工厂函数中
b.如有任何代码检查子类的类型,先用提炼函数把类型检查逻辑包装起来,然后将其搬移到基类
c.新建一个属性表示子类类型
d.将原本用来判断子类类型的属性使用新建的子类类型
e.删除子类

9.8. 提炼基类

目的: 两个类在做相似的事情
实现:
a.新建类
b.使用构造函数本体上移’函数上移和属性上移手法,提取子类的共同元素
c.确保子类中的相同成分都上移到基类
d.考虑将调用的地方使用基类的接口

9.9. 折叠继承体系

目的: 基类和子类差异很小时,将两者合并
实现:
a.使用属性上移、属性下移、函数上移和函数下移将所有元素都移动到一个类中
b.即将删除的类的引用点修改为合并后的类
c.移除将删除的类

9.10. 以委托取代子类

目的:
a.降低继承之间的强依赖关系
b.可以使接口更清晰、减少耦合
实现:
a.如果构造函数有多个调用者,首先用以工厂函数取代构造函数,把构造函数包装起来
b.创建空的委托类,构造函数接受所有子类特有的数据项,并经常以参数的形式接受一个指向基类的引用
c.基类中添加属性,用于安放委托对象
d.修改子类的创建逻辑,使其初始化上述委托字段,放入委托对象示例
e.选择一个子类的函数移入委托类
f.使用搬移手法搬移上述函数
g.如果被搬移的源函数还在子类之外被调用了,就把留在源类中的委托代码从子类移到超类,并在委托代码之前加上卫语句,检查委托对象存在。如果子类之外已经没有其他调用者,就用移除死代码去掉已经没人使、用的委托代码。
h.重复上述步骤,直到子类中所有的函数都搬移到委托类

9.11. 以委托取代基类

目的:
a.通过继承来复用现有的功能。
b.基类的一些函数对子类不适用时,不应该使用继承获取基类的功能
c.用到现有类的部分功能时使用委托
实现:
a.子类中新建一个属性,使其引用基类的一个对象,并将这个委托引用初始化为基类的新示例
b.针对基类的每个函数,在子类中创建转发函数,将调用请求转发给委托引用
c.当所有超类函数都被转发函数覆写后,就可以去掉继承关系

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1009851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用cpolar配合Plex搭建私人媒体站

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频,已经算是生活中稀松平常的场景了,特别是各…

如何开启Win10虚拟机Hyper-V功能

操作步骤: 使用前提: 1、确保系统是 Windows 10 专业版/企业版/教育版,且必须是64位操作系统才支持。 提示:Win10家庭版不支持hyper-v。 2、使用Hyper-V需要cpu支持虚拟化并处于开启状态。 3、硬件要求及如何验证硬件兼容性: 硬件…

Hive 使用达梦DM8 无法识别 “COMMENT” 问题

将达梦数据库驱动 DmJdbcDriver18-8.1.2.192.jar 导入到 hive 的 lib 文件夹下 修改 hive 配置文件&#xff0c;增加 dm 数据库相关信息 <property><name>javax.jdo.option.ConnectionURL</name><value>jdbc:dm://127.0.0.1:5236?SCHEMAhive</val…

vmware官网下载(VMware workstation 下载与安装)

Oracle RAC 安装配置part1 注册VMware 个人帐号 浏览器打开官方网站 下面是主页&#xff0c;单击Customer connter 单击立即注册 填入相关信息注册完成 需要到您邮件里的激活一下帐号 注册完成 下载VMware Workstation 单击转到CUSTOMER CONNECT (您也可以在主页login…

修饰符的笔记

修饰符 完整的教程参考下面的链接 菜鸟教程-修饰符 下面是我总结的比较精简的内容 常见的修饰符有权限修饰符和非权限修饰符 权限修饰符 权限修饰符就是我们所熟知的 private&#xff0c;public&#xff0c;protect&#xff0c;default&#xff08;什么都不写&#xff0c;默…

华为云云耀云服务器L实例评测|基于华为云云耀云服务器L实例搭建EMQX大规模分布式 MQTT 消息服务器场景体验

文章目录 前言一、&#x1f604;华为云云耀服务器二、&#x1f604;产品实例创建相关1、&#x1f9e8;开通华为云云耀服务器2、&#x1f9e8;创建华为云云耀服务器实例3、&#x1f9e8;终端登录4、&#x1f9e8;华为云云耀云服务器密码重置 三、&#x1f604;安装开源产品EMQX四…

python diffusers StableDiffusionXLPipeline 离线使用

下载sd_xl_base_1.0.safetensors https://huggingface.co/runwayml/stable-diffusion-v1-5/tree/main 我这下载后放到项目 models 里 model_path "./models/v1-5-pruned-emaonly.safetensors" # model_path "./models/v1-5-pruned.safetensors" # model…

人工智能创业,2023爆火风口项目:实景无人直播帮实体店精准获客

软件图片素材来自于公众号&#xff1a;生财风暴 关注进行领取价值1000元的采集软件&#xff0c;和呆头鹅批量剪辑和矩阵管理系统演示 把AI和直播结合在一起的实景自动直播你知道吗&#xff1f;如果提起人工智能创业项目啊&#xff0c;你还只知道CHHGPP的话&#xff0c;那不妨把…

光伏浪涌保护器综合应用工程方案

光伏浪涌保护器是一种用于保护光伏发电系统中的设备和线路免受雷电或其他瞬态过电压的影响的装置。光伏发电系统由于其分布式、室外、大面积等特点&#xff0c;容易受到雷电直击或感应&#xff0c;导致系统内部产生高能量的浪涌电流和电压&#xff0c;从而损坏光伏组件、逆变器…

竞赛选题 基于大数据的时间序列股价预测分析与可视化 - lstm

文章目录 1 前言2 时间序列的由来2.1 四种模型的名称&#xff1a; 3 数据预览4 理论公式4.1 协方差4.2 相关系数4.3 scikit-learn计算相关性 5 金融数据的时序分析5.1 数据概况5.2 序列变化情况计算 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &…

【Git】Git 快照 Snapshot

Git 快照 Snapshot 在对 Git 基础的学习过程中&#xff0c;我们了解了 Git 仓库的基本结构&#xff1a; 工作目录暂存区版本库&#xff0c;即 .git 仓库 下面我们就通过一次修改、暂存以及提交的工作流程&#xff0c;来理解快照&#xff08;Snapshot&#xff09;的概念。 现…

基于Docker的JMeter分布式压测实战讲解

一个JMeter实例可能无法产生足够的负载来对你的应用程序进行压力测试。如本网站所示&#xff0c;一个JMeter实例将能够控制许多其他的远程JMeter实例&#xff0c;并对你的应用程序产生更大的负载。JMeter使用Java RMI[远程方法调用]来与分布式网络中的对象进行交互。JMeter主站…

Linux:基础开发工具之Makefile和缓冲区的基本概念

文章目录 动静态库自动化构建代码缓冲区 动静态库 首先要知道什么是链接&#xff1a; C程序中&#xff0c;并没有定义printf的函数实现,且在预编译中包含的stdio.h中也只有该函数的声明,而没有定义函数的实现 系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没…

旋转偏心裁切刀切向跟踪及半径补偿

1 裁刀半径补偿问题的提出 偏心裁刀一般皮革和纸箱行业用的比较多&#xff0c;它适用于裁切比较厚的材料。对于如图1所示的偏心裁刀&#xff0c;它的刀尖和旋转轴(也就是刀心)存在一个距离&#xff0c;设为半径r。由于改刀刀刃有方向&#xff0c;所以用该刀去切割直线时&#…

使用nvm管理node.js

使用nvm管理node.js 一、简介 nvm是一个node的版本管理工具。可以在多种系统上管理Node.js 版本的工具。使用 NVM&#xff0c;可以轻松地切换不同版本的Node.js&#xff0c;并方便地管理不同版本的全局包和本地包。 二、安装与下载 1.删除原有node.js 首先需要卸载已安装的…

免费音乐下载网站分享(MP3文件格式)

免费音乐下载网站分享&#xff08;MP3文件格式&#xff09; 最近需要下载一些歌曲&#xff0c;发现很多音乐app上下载文件都需要vip&#xff0c;再上网查询了一番&#xff0c;最后发现了一个宝藏网站&#xff0c;可以免费下载各种格式的MP3文件&#xff0c;在这里给大家分享一…

建设数字孪生智慧城市是未来城市的重要增长点

中国国家创新与发展战略研究会学术委员会常务副主席、重庆市原市长黄奇帆在《瞭望》撰文指出&#xff1a; AI时代的城市是由实体空间和数字空间组成的数字孪生城市&#xff0c;要充分重视对数字空间的治理。随着城市数字化进程的加快&#xff0c;城市、企业、个人开始形成多样化…

软件需求怎么写?

前言&#xff1a;一般来说&#xff0c;软件产品的需求人员的主要输出物就是软件需求&#xff0c;如果这个软件产品就XX系统&#xff0c;人们口中的“系统需求”和“软件需求”就没有什么区别了。在车企行业&#xff0c;推行这ASPICE体系&#xff0c;在这个体系中明确申请了系统…

2023工博会,正运动开放式激光振镜运动控制器应用预览(三)

展会倒计时&#xff1a;4天 本次的中国国际工业博览会正运动技术将携开放式激光振镜运动控制器ZMC408SCAN-V2亮相。 •绿色生产&#xff0c;减少材料和资源浪费&#xff0c;提升可持续性&#xff1b; •节省多套硬件成本&#xff0c;替代传统的激光加工系统&#xff0c;降低项…

【深度学习】树莓派Zero w深度学习模型Python推理

在机器学习开发过程中&#xff0c;当模型训练好后&#xff0c;接下来就要进行模型推理了&#xff0c;根据部署环境可分为三类场景&#xff1a; 边缘计算&#xff1a;一般指手机&#xff0c;嵌入式设备&#xff0c;直接在数据生成的设备上进行推理&#xff0c;因为能避免将采集…