首页 > 其他分享 >重构-重构方式

重构-重构方式

时间:2024-04-30 17:56:17浏览次数:13  
标签:动机 重构 函数 方式 对象 子类 一个 概述

1、重新组织函数

1.1  提炼函数 Extract Method

* 概述:将一段代码放入一个独立函数中,并让函数名称解释该函数的用途。
* 动机:厌恶过长的代码。如果函数过长,则很难被理解(用途不清晰,需要大段的注释描述意图),测试也困难;相反,如果一个函数够小、有清晰意图的命名,则被复用的机会更大,函数也更容易被修改。
* 做法:编写一个新函数,根据这个函数的意图来命名 -- 如果能对一段代码给予一个更好表达其意图的命名,都可以提炼她。
* 注意:这个重构手法最困难的地方在于提炼局部变量:
1. 如果需要读取源函数的局部变量,则可将其作为参数传入新函数。
2. 如果某些局部变量需要在目标函数后面使用,可能需要新函数返回该局部变量改变的值。
3. 对于需要修改多个局部变量的情况,则可不提炼此段代码,或者将所有临时变量抽取为一个类。

1.2  内联函数 Inline Method

* 概述:在函数调用点插入函数本体,然后移除该函数
* 动机:1.有些函数的内部代码比函数名本身更加清晰易读,则可以内联。2.函数调用混乱,可以先全部内联在一起,然后重新提炼。
* 做法:检查多态覆盖,检查函数引用,引用点直接替换为函数。

1.3. 内联临时变量 Inline Temp

* 概述:将所有对该变量的引用动作,替换为对她赋值表达式。
* 动机:临时变量可能妨碍其他的内联手法。
* 做法:确保只被一个简单的表达式赋值了一次

1.4. 以查询取代临时变量 Replace Temp with Query

* 概述:程序以某个临时变量保存表达式的运算结果。则将一个表达式提炼到一个函数中,将将对这个临时变量的引用替换为对新函数的调用
* 动机:临时变量总是驱使你写更长的函数,因为临时变量只在函数内可以访问到,所以将一些运算得到的临时变量提炼为一个函数,可以在任何时候访问。ps: 导致重复计算,但是使代码清晰。
* 做法:确保该临时变量只被赋值一次,然后用新函数替换引用处。可以先将该临时变量声明为final,让编译器保证只赋值一次。

ps: 1,2,3,4的操作都让代码结构变得更加清晰、简洁,使得函数名称本身便具有自注释功能。

1.5. 引入解释性变量 Introduce Explaining Variable

* 概述:将复杂表达式(或其中一部分)的结果放入一个临时变量,以这个变量的名称来解释表达式用于
* 动机:将一个复杂的表达式进行拆解,用临时变量的名称来表达部分过程的意图。

1.6. 分解临时变量 Split Temporary Variable

* 概述:程序中某个临时变量有多个用途,既不是循环变量,也不是收集计算结果变量,则应该对每个用途编写一个临时变量
* 动机:临时变量有不同的用途,或者由不同含义的操作计算而得到,则不应该使用同一临时变量。

ps: 5,6的拆解都是为了使得表达式的意图更加清晰,各种复用临时变量,复杂计算过程等代码都可能让代码变得含混。

1.7. 移除对参数的赋值 Remove Assignments to Parameters

* 概述:代码对一个参数进行赋值,应该用一个临时变量取代参数的位置。
* 动机:对参数的赋值可能改变其引用的对象(引用参数), 从而丢失了原来的入参引用。所以将参数赋值给一个临时变量以改变这种含混性。

1.8. 以函数对象取代函数 Replace Method with Method Object

* 概述: 有一个大型函数,其中过多的局部变量无法进行函数提炼,则将这个放入一个单独对象中,这样局部变量就成了对象的成员变量,然后在对象内将大型函数分解为多个小函数
* 动机: 局部变量太复杂,根本无法拆解, 则使用一个对象来描述该方法。 -- 该对象需要有一个与方法意图对应的命名。
* 做法: 1. 建立一个新类,并将原类作为该类的一个常量引用
2. 新类中提供构造函数接收原对象,及原函数的所有参数。
3. 在新类中提供一个函数,将原函数的代码复制到其中,通过原对象引用相应变量。

1.9. 替换算法 Substitute Algorithm

* 概述:将函数本体替换为另一个算法
* 动机:原函数的算法实现比较复杂,切不清晰,或者性能不好。如果你发现有更好的实现方式,勇敢的壮士断腕,替换该算法吧。


2、在对象间搬移特性


2.1.  搬移函数 Move Method

* 概述:将一个函数移动到另一个类中去
* 动机:类中一个函数与其他的类进行频繁的交流,而与本类交流较少,则将函数移到另一个类中,从而减少调用关系。
* 做法:搬移一个或者一组相关的函数;检查源类的子类和超类是否有该函数的其他声明(覆盖,委托等问题);修改源函数使之成为一个委托函数。

2.2. 搬移字段 Move Field

* 概述:将类中一个字段搬移到其他类中
* 动机:本类中的一个字段,被其他类中的字段使用次数更多,搬移字段减少调用关系,间接提高信息隐藏性。
* 做法:如果字段被很多地方引用,则先使用自我封装(Self-Encapsulation)将字段变成设/取值函数,然后搬移字段,最后修改设/取值函数为一个委托。

2.3. 提炼类 Extract Class

* 概述:建立一个新的类,将相关的字段和函数从旧类中搬移到新类
* 动机:1. 一个类的方法过多、责任过大做了由两个类做的事情。主要是由于当前类对于场景的抽象不够细致,导致过于复杂。
2. 一个类中的数个特性朝着不同方向发展,从而变得无所关联。最主要的表现是子类化,某些特性需以一种方式子类化,某些特性以另一种方式子类化 
* 做法:从一个类中搬移函数和字段到一个新类中,使得新类作为一个事物的更细致抽象。
* 例如:一个person类有地址字段和函数,比较复杂。可以将地址抽象为一个新类,然后person调用这个地址类。

2.4. 内联类 Inline Class

* 概述: 将一个类的所有特性搬移到另一个类中,然后移除该类
* 动机: 这个类没有做太多事情
* 做法: 上述的反过程

2.5. 隐藏委托关系 Hide Delegate

* 概述: 在服务类建立客户所需的所有函数,用以隐藏委托关系
* 动机: 对内部进行封装,通过一个委托类来调用其他对象,这样调用者可以不用了解内部变化,从而降低修改风险。
* 做法: 正如设计模式中的代理模式等, 不仅隐藏调用关系,还可以对调用进行统计等管理。

2.6. 移除中间人 Remove Middle Man

* 概述: 直接调用受托类
* 动机: 某个类做了过多的简单委托,每添加一个新特性都需要在委托类中进行相应添加。
* 做法: 上述反过程

2.7. 引入外加函数 Introduce Foreign Method

* 概述: 在客户类中建立一个函数,并以第一参数形式传入一个服务类实例
* 动机: 由于代码修改权不在自己手里,但是需要为某个类添加新特性。可以在外层写这样一个函数,并所需要的值都以参数传入。
* 做法: 在拥有代码所有权后,将此函数返回她该去的地方

2.8. 引入本地扩展 Introduce Local Extension

* 概述: 当需要为一个服务类提供一些额外的函数,但是你无法修改。可建立一个新类,使她包含这些额外的函数。让这个扩展类成为源类的子类或者包装类
* 动机: 为类做扩展,可以使用装饰模式或者继承,使用继承请确保必定有继承关系。

ps: 从以上重构方法可以看出,其最终都是为了减少了代码中的调用关系,提高代码的意图表达。

3、 重新组织数据

3.1. 自封装字段 &封装字段 &封装集合 Self Encapsulate Field & Encapsulate Field & Encapsulate Collection

* 概述:将字段进行封装,然后提供设/取值函数,对于集合取值只能返回可读的副本
* 动机:隐藏内部数据接口,这样就算字段数据结构发生变化,只需要调整设/取值函数,从而不影响其他类的调用; 对于集合需要防止外部对引用的修改; 方便上syncrized关键字

3.2. 以对象取代数据值 Replace Data Value with Object

* 概述:将数据项变为字段
* 动机:数据项为对一个事物属性的描述,你无法估量这个场景下该事物属性的复杂性,所以用一个类构建该数据项,能够抗击未来的变化。

3.3. 将值对象改为引用对象 Change Value to Reference

* 概述:将这个值对象变成引用对象
* 动机:对一个对象修改的数据,能够影响引用这个对象的地方

3.4. 将对象引用改为值对象 Change Reference to Value

* 概述:将引用对象变为一个值对象
* 动机:利用其不可变性质

3.5. 用对象取代数组 Replace Array with Object

* 概述:以对象替换数组,对于数组中的每个元素,以一个字段来表示
* 动机:有些其他语言,将一个对象的属性存放在一个数组中了。将其重构为对象,从而可用字段名逐一描述

3.6. 复制被监视数据 Duplicate Observed Data

* 概述:将数据复制到一个领域对象中,建立一个observer模式,用于同步领域对象和GUI对象内的数据
* 动机:分离界面显示和业务逻辑,在界面更新数据的时候同步更新领域对象数据

3.7. 将单向关联改为双向关联 Change Unidirectional Association to Bidirectional

* 概述:添加一个反向指针,并使修改函数能够同时更新两条连接
* 动机:使得两个类相互依赖,方便访问对方的成员。 虽然方便,但是调用关系复杂,容易产生僵尸对象,修改时相互影响。

3.8. 将双向关联改为单项关联 Change Bidirectional Association to Unidirectional

* 概述:去掉不必要的关联
* 动机:降低类间复杂度

3.9. 以字面常量取代魔法数 Replace Magic Number with Symbolic Constant

* 概述:创造一个常量,根据其意义命名,并将有特殊意义的字面数值替换为这个常量
* 动机:魔数-有特殊意义的数字,使用一个常量来表示这个数字的意义

3.10. 以数据类取代记录 Replace Record with Data Class

* 概述:面对传统编程的记录结构,为该记录创建一个'哑'数据对象
* 动机:与数据库的记录进行交互,ORM

3.11. 以类取代类型码 Replace Type Code with Class

* 概述:用一个新的类替换该类中用常量数值表示的类型
* 动机:其实就是使用枚举类型来代替常量枚举,这样使得类型被更有意义的描述,并且提供了对应的操作函数,对于复杂的类型十分方便。

3.12. 以子类取代类型码 Replace Type Code with Subclasses

* 概述:为类型码建立一个继承宿主类的子类,将根据这个类型码执行的操作,放入到一个子类中去。
* 动机:将不同类型码的操作都放入对应的子类中,这样不经让代码更清晰,同时防止错乱。

3.13. 用状态或者策略取代类型码 Replace Type Code with State/Strategy

* 概述: 类中有类型码会影响类的行为,但是无法通过继承手法来消除
* 动机: 无法直接继承宿主类,则自己定义一个抽象的类去继承,与上述一致。

3.14. 以字段取代子类 Replace Subclass with Fields

* 概述: 各个子类的差别不是打,只在一些常量数据上
* 动机: 将子类中的共同行为搬移到超类,并在超类中定义这组差异的常量。减少无必要的编码

4、 简化条件表达式

4.1. 分解条件表达式 Decompose Conditional

* 概述:有一个复杂的条件表达式,从if then else中分别提炼出独立函数
* 动机:复杂的条件逻辑常常导致代码复杂度的提高,提炼不同分支,并按照意图命名,是代码可读性和清晰提高

4.2. 合并条件表达式 Consolidate Conditional Expression

* 概述:有一些列的条件测试,但是都返回相同结果,则合并这些表达式
* 动机:有一些表达式虽然条件不同,但是结果一样,将这些条件合并

4.3. 合并重复的条件片段 Consolidate Duplicate Conditional Fragment

* 概述:条件表达式的每个分支有相同的一段代码,将这些代码抽取出来,放入表达式之外
* 动机:将每个分支都执行的代码,搬移到代码执行分支之外,简化分支表达式,以使代码更加清晰。

4.4. 以卫语句取代嵌套条件表达式 Replace Nested Conditional with Guard Clauses

* 概述:用卫语句处理特殊情况,卫语句就是if then return这种形式的表达式,常用于函数入口处,保护函数体只接受正确的参数。
* 动机:对于情况比较特使的逻辑,使用此表达式可以简化代码,使之更加清晰

4.5. 以多态取代条件表达式 Replace Conditional with Polymorphism

* 概述:将条件表达式的分支,放入每个子类的覆写函数中,并将原始函数声明为abstract
* 动机:子类覆盖超类的条件表达式,在子类中只关注与自己相关的条件和行为。这样将集中在一起的逻辑打散到各个子类中。

4.6. 引入Null对象 Introduce Null Object

* 概述:当需要再三检查对象是否为null,则将null值替换为一个null对象
* 动机:为原类建立一个子类,其行为就是原类的null版本,可以方便多态的进行。空对象的存在,可以省去判空逻辑(判空逻辑只写在Null对象内部,其他写在超类)。

4.7. 引入断言 Introduece Assertion

* 概述:某段代码需要对程序状态做出某种假设,以断言明确表现这种假设
* 动机:断言可以帮助阅读程序代码所做的假设,可以在调试和调试,测试中广泛使用

ps: 这些方法都为了减少条件表达式的复杂性,提高其清晰度和意图,使用多态和空对象的方式,在一定程度上为编程提供方便。

5、 简化函数调用

5.1. 函数改名 Rename Method

* 概述:修改函数名称
* 动机:函数的名称未能表示函数的意图

5.2. 增加参数 Add Parameter

* 概述:为函数添加一个对象参数,让这个对象带进函数所需的信息
* 动机:某个函数需要从调用端获取更多的信息

5.3. 移除参数 Remove Parameter

* 概述:将函数参数移除
* 动机:函数本体不在需要某个参数,而参数列表却有

5.4. 将查询函数和修改函数分离 Separate Query from modifier

* 概述:建立两个不同的函数,其中一个负责修改,另一个负责查询
* 动机:获取值得时候会修改函数值,会使得其他只需要取状态值得调用者关心更多的东西,从而产生副作用

5.5. 令函数携带参数 Parameterize Method

* 概述:建立单一函数,以参数表达那些不同的值。
* 动机:多个函数做着类似的工作,只是因为少数几参数,或者参数个数不同。则可以使用一个单一函数将他们统一起来,为这个函数增加携带参数

5.6. 以明确函数取代参数 Replace Parameter with Explicit Methods

* 概述:针对该参数的每一个可能值,建立一个独立函数
* 动机:有一个函数,取决于不同的参数值而采取不同的行为,这时可以为每个独立之建立一个独立函数。 某种程度与上一条相反

5.7. 保持对象完整 Preserve Whole Object

* 概述:改为传递整个对象
* 动机:从某个对象中取出若干值,并将他们作为某一次函数调用参数的时候,可以将参数修改为传递整个对象,以减少参数列表和对抗未来改动的风险。

5.8. 以函数取代参数 Replace Parameter with Methods

* 概述:让参数接受者去除该项参数,并直接调用前一个函数
* 动机:如果函数可以通过其他途径取得参数值,那么久不应该通过参数值获取。这样可以减少参数列表,从而减少局部变量。

5.9. 引入参数对象 Introduce Parameter Object

* 概述:以一个对象取代这些参数
* 动机:某些参数总是很自然的同时出现,当一组参数总是被同时传递给多个函数时候,则将这些参数整合成一个对象

5.10. 移除设置函数 Remove Setting Method

* 概述:去掉字段中的所有设置函数
* 动机:对象中的某个字段只应该在对象创建的时候被设置,然后就不再改变,则不因该提供设值函数,否则可能导致其值被修改,且容易混淆

5.11. 以工厂函数取代构造函数 Replace Constructor with Factory Method

* 概述:将构造函数替换为工厂函数
* 动机:通过静态工厂函数来生产对象,从而将对象生产权利把控在本类中,调用者完成不需要关系这个新对象的构建过程

5.12. 封装向下转形 Encapsulate Downcast

* 概述:将向下转形动作移到函数中
* 动机:某个函数返回的对象需要调用者自己强转类型。这是可以将强转类型封装在函数内部,使得调用者无需关心实际类型。需要该函数返回类型有限且确定。

5.13. 以异常取代错误码 Replace Error Code with Exception

* 概述:将错误码改用异常
* 动机:抛出异常能够更加清楚知道代码执行过程产生的问题。

5.14. 以测试取代异常 Replace Exception with Test

* 概述:修改调用者,使它在调用函数之前先做检查
* 动机:异常只应该用于哪些产生意料之外的错误行为,而不应该成为条件检查的替代品。可以提供一个可重复执行的测试函数,让调用者者调用前先检查某个条件,在测试函数中处理try catch

ps: 从上述方法可以看出,其最终都是为了减少函数间传参的复杂度,从而减少了局部变量个数,也就提高的代码清晰度。

6、处理概括关系

6.1. 字段上移 Pull Up Field

* 概述:将字段移到超类
* 动机:两个子类拥有相同的字段

函数上移 Pull Up Method

* 概述:将函数移动至超类
* 动机:有些函数各个子类中产生完全相同的结果

6.3. 构造函数本体上移 Pull Up Constructor Body

* 概述:在超类中新建一个构造函数,并在子类中调用她
* 动机:各个子类中拥有本体几乎一致的构造函数

6.4. 字段下移 Push Down Field

* 概述:将这个字段移动到需要她的类中
* 动机:超类中某个字段只被部分子类用到,让数据与操作在同一个类中。

6.5. 提炼子类 Extract Subclass

* 概述:新建一个子类,将只被某些实例用到特性移动该子类中
* 动机:子类划分不够具体,不够细致,导致某个类包含过多本不该自己管理的东西,这是可以提炼一个新的子类。

6.6. 提炼超类 Extract Superclass

* 概述:为两个类建立一个超类,将相同的特性移至超类
* 动机:两个类具有相似的特性

6.7. 提炼接口 Extract Interface

* 概述:将相同的子集提炼到一个独立接口中
* 动机:1. 若干客户端使用类接口中的同一子集;2. 两个类的接口有部分相同。这两种情况都可以抽出一套共有接口。

6.8. 折叠继承体系 Collapse Hierachy

* 概述:将继承体系合为一体,以消除继承体系
* 动机:超类和子类,并没有太大的区别。 ps:

6.9. 塑造模板函数 Form Template Method

* 概述:模板模式,不再概述

6.10. 以委托取代继承 Replace Inheritance with Delegation

* 概述:装饰模式,不再概述
* 动机:没有继承关系的两个类使用继承,不仅没有代码复用,还继承了父类大堆不相干方法,容易造成混淆;对于抽象的方法还必须覆写。而装饰模式可以自主选择需要复用的方法。ps:非继承关系的扩展请使用装饰模式;有继承关系的扩展请使用继承

6.11. 以继承取代委托 Replace Delegation with Inheritance

* 概述:对于有继承体系的仍然使用继承体系,这样可以复用父类的方法和字段。

7、大型重构

7.1. 梳理并分解继承体系 Tease Apart Inheritance

* 概述:建立两个继承体系,并通过委托关系让其中一个可以调用另一个
* 动机:某个继承体系同时承担两项责任,将此继承体系拆解,使得抽象的分类更清楚、细致从而提高类的复用率,并使代码更简洁。

7.2. 将过程设计转化为对象设计 Convert Procedural Design to Objects

* 概述:将数据记录变成对象,将大块的行为分成小块,并将行为移入相关的对象中
* 动机:将面向过程的代码重构为面向对象的风格。

7.3. 将领域和表述/显示分离 Separate Domain from Presentation

* 概述:将领域逻辑分离出来,为他们建立独立的领域类
* 动机:从GUI中抽离领域逻辑,从而做到与显示的分离。如MVC模式

7.4. 提炼继承体系 Extract Hierachy

* 概述:建立继承体系,以一个子类表示一种特殊情况
* 动机:有一个类做了太多的工作,其中一部分是大量的条件表达式完成的。

 

标签:动机,重构,函数,方式,对象,子类,一个,概述
From: https://www.cnblogs.com/use-D/p/18168492

相关文章

  • base64转file文件的两种方式
    base64加载图片文件使用base64可以不发送请求将图片文件转换为base64格式的链接渲染到图片上,减少服务器访问次数,下面是base64加载图片的方式document.getElementById("front-file").onchange=(e)=>{constfile=e.target.files[0];constreader=newFileReader()......
  • simpread-课程 21:API 项目重构
    项目结构重构1.1Electric.DbMigrator存在的问题我们先来看下,后台API项目的目录结构。其中Electric.DbMigrator,这个项目作用是用来做数据库迁移的,但是同时也会被其他项目引用,还有这个项目类型还是WebAPI类型的。所以存在以下的几个问题:1、项目功能重合:数据库迁移和数......
  • OnlineJudge的正确打开方式
    1.不能依赖OJ的反馈做题:a)      应该仔细读题,完全理解题意之后自行设计样例与反例,不应该靠OJ给出的不通过样例做题;b)      读题要边读边记录灵感或是注意事项,否则设计算法时可能会忘记;c)      应该在纸上用样例模拟几次,并写......
  • 如何通过前后端交互的方式制作Excel报表
    前言Excel拥有在办公领域最广泛的受众群体,以其强大的数据处理和可视化功能,成了无可替代的工具。它不仅可以呈现数据清晰明了,还能进行数据分析、图表制作和数据透视等操作,为用户提供了全面的数据展示和分析能力。今天小编就为大家介绍一下,如何通过葡萄城公司的纯前端表格控件Spre......
  • stm32F07 HAL 库 通过定时器方式实现呼吸灯 自定义呼吸灯函数 (以参数方式设置io
    效果: 1、通过Stm32CubMX开启定时器、设置对应的io口,然后生成工程STM32CubeMX|STM32HAL库方式的微秒延时函数  2、自定义呼吸灯函数代码://呼吸灯函数//GPIO_TypeDef*GPIOx:GPIO组(A-G)//uint16_tGPIO_Pin:IO口(GPIO_Pin_0--GPIO_Pin_16)//......
  • CentOS安装MySQL的两种方式——RPM和YUM
    0、首先确认是否安装过MySQLyumlistinstalled|grepmysqlrpm-qa|grepmysql若果有安装,需要先删除旧版本。yumremovemysql一、通过官网下载tar压缩包(或者直接下载RPM安装包)1.打开MySQL官网下载地址,选择适合自己的版本,下载tar压缩包。2.通过WinSCP等工具上传到CentO......
  • 镜像:数字时代的自我呈现与虚拟重构
    在数字时代,镜像的概念已超越了传统的物理范畴,它不再仅仅是水面或镜面上的反射,而是深入到了我们日常生活的各个角落。作为信息技术发展的重要成果,数字镜像已成为人们认识自我、展现自我和重塑自我的重要工具。#人工智能#AutoDL#AutoDL算力云#GpuMall智算云#阿里云立即免费体验:htt......
  • 以后台方式启动RealVNC
    以后台方式启动RealVNC运行C:\ProgramFiles\RealVNC\VNCServer\vnclicensewiz.exe离线注册。以Service模式启动(后台服务的方式)C:\ProgramFiles\RealVNC\VNCServer>vncserver.exe-service-start改变VNC鉴权方式:修改VNC密码:C:\ProgramFiles\RealVNC\VNCSer......
  • linx使用命令还原数据库(source还原方式)
    进入到数据库mysql-udatatablename-p//参数解析:datatablename是连接数据库的用户输入数据库密码: 成功进入数据库: 2、可以查看当前用户有哪些数据库权限 showdatabases;3、进入到指定的数据库usetest;//参数解析:test-是数据库名称4、查看当前数据......
  • 传统FTP为何不好用了?替代FTP传文件的方式有哪些?
    FTP(文件传输协议)是一种广泛使用的网络协议,用于在网络上的服务器和客户端之间传输文件。它具有一些明显的优点,比如易于使用、支持文件的上传和下载、以及能够处理多个文件和目录的传输。然而,随着时间的推移和技术的发展,FTP也暴露出一些局限性和缺点,这导致一些企业寻求替代FTP传文件......