重构改善既有代码

重构改善既有代码

  • 第一次作某件事情的时候尽管去作,第二次作相似的事会产生反感,第三次再作相似的事,你就应该重构。
  • 小型函数优美动人
  • 一个类最好是常量类,任何的改变都是调用该类自己的接口实现。

0 坏代码的味道

一、重复代码

  • Duplicated Code
  • 同一类中的两个函数含有相同的表达式,提取到方法
  • 互为兄弟的子类含有相同表达式,将两个子类的相同代码提取方法推入超类
    • 若是有类似代码,经过提炼方法将类似和差别部分分割开,并使用疏凿模板方法,并将模板方法上移到超类中。
  • 若是两个绝不相关的类出现重复代码,将重复代码提炼到一个提炼类中,两个类都使用这个提炼类。

二、过长函数

  • Long Method
  • 间接层所能带来的所有利益——解释能力、共享能力、选择能力
  • 小函数的价值是巨大的
  • 每当感受须要注释来讲明什么的时候,就须要把说明的东西写进一个独立函数中,并以其用途命名。
  • 函数内有大量的临时变量和参数。须要运用提炼方法,能够将临时变量做为参数传入,也可使用以查询替代临时变量,当方法参数特别多的时候能够提炼参数类,传递参数类实体。若是这么作还有不少的参数,那么就应该用方法对象来取代方法了。
  • 选择提炼哪一段代码
    • 寻找注释,有注释的地方都在提醒你须要提炼方法了,注释名称就是很好的方法名
    • 条件表达式和循环也是型号,能够用 分解条件表达式,循环应该将循环中的代码提炼到独立函数中。

三、过大的类

  • Large Class
  • 若是单个类作太可能是事情,每每会致使出现太多的实例变量,一旦如此,重复代码就接踵而至了。
  • 可使用提炼类将几个变量和方法提炼出来,若是数个变量存在着相同的前缀或字尾,就觉得着有机会能够把它们提炼到某个组件中。若是这个组件适合一个子类,还可使用提炼子类。
  • 若是一个拥有太多代码,能够先肯定客户端如何使用它们,而后运用提炼接口,为每一种使用方法提炼出一个接口,这能够看清楚如何分解这个类。
  • 若是超大类是一个GUI类,能够把数据和行为移到一个独立的领域对象去,可能须要两边保留一些重复代码,并保持两边同步。

四、过长的参数列

  • Long Parameter List
  • 若是向已有的对象发出一条请求就能够取代一个参数,那么就可使用用方法取代参数方法。
  • 还可使用保持整个对象,传递整个对象,
  • 提炼参数对象
  • 形成函数关联须要慎重考虑

五、发散式变化

  • Divergent Chane
  • 软件再怎么说就应该是软的,一旦须要修改,但愿可以跳到系统的某一点,只在该处作修改。若是不能的化就有一种刺鼻味道了。
  • 某个类常常由于不一样缘由在不一样不一样方向上发生变化发散式变化就出现了,
  • 一旦出现这种发散式变化那么就须要将对象分解成多个对象或者会更好,当出现多个类后还能够提炼超类等。

六、霰弹式修改

  • Shotgun Surgery
  • 正对某一项变化须要在许多不一样类种作出须要小修改,所面临的味道就是霰弹式修改,
  • 这种状况应该使用移动方法和移动字段,把全部修改的代码放进同一个类,若是没有现存的类能够按值这些代码就创造一个,使用内联类能够将一系列相关行为放进同一个类。
  • 这也可能形成少许的发散式变化,

七、依恋情结

  • Feature Envy
  • 对象技术的所有要点在于:这是一种将数据和对数据的操做行为包装在一块儿的技术,有一中经典的气味是:函数对某个类的兴趣高于对本身所处类的兴趣。
  • 使用移动方法把某些方法移动带它该去的地方,有的时候还须要提炼方法
  • 若是某个函数须要须要几个类的功能,判断哪一个类拥有最多被此函数使用的数据,而后就把这个函数和那些数据摆在一块儿,能够先将函数分解成多个较小函数分别置于不一样地点。
  • 将老是一块儿变化的东西放在一块,数据和引用这些数据的行为老是一块儿变化的。
  • 策略和访问者模式能够轻松修改函数行为,付出了多一层的代价

八、数据泥团

  • Data Clumps
  • 数据项会三五成群出现。
  • 若是删除总舵数据中的一项,其余数据有没有失去意义,若是它们再也不有意义,就是一个明确的信号,应该产生一个新对象。

九、基本类型偏执

  • Primitive Obsession
  • 结构类型容许你将数据组织成有意义的形式,对象的极大价值在于打破了横亘于基本数据和较大类之间的界限。
  • 积极的使用使用对象替换数据值,用类替换类型码,用状态/策略模式替代类型码

十、swithc惊悚现身

  • Switch Statements
  • 面向对象程序的最明显特征就是少用switch,使用switch的问题在于重复,在修改上,若是switch散布于不一样地点,就要添加新的case子句
  • 若是看到switch语句的时候须要考虑用多态来替换它,问题在于多态出如今哪儿
  • 使用提炼函数将switch提炼到独立函数中,再用移动方法将它搬移到须要多态性的类中,用子类替代类型码或者使用state/strategy替代类型码,完成以后再用使用多态替代条件。

十一、平行继承体系

  • Parallel Inheritance Hierarchies
  • 若是为某个类增长一个子类的时候必需要为另外一类相应增长一个子类。
  • 若是某个继承体系的类名称前缀和两一个继承体系的类的名称前缀彻底相同
  • 让一个继承体系的实例引用另外一个继承体系的实例,再使用移动方法和字段,就能够将引用端的继承体系消除。

十二、冗赘类

  • Lazy Class
  • 建立的每一个类都有人去理解它维护它,若是一个类不值得其身价就应该消失。

1三、夸夸其谈的将来性

  • Speculative Generality
  • 总有一天须要作这件事,企图以各式各样的勾子和特殊状况来处理一些非必要事情会形成程序难以理解。不须要

1四、使人迷惑的暂时字段

  • Temporary Field
  • 某个实例变量仅为某种特定状况而设置。
  • 使用提炼类给这些孤儿创造一个家,而后把全部和这个变量相关的代码都放进这个新家,还可使用空对象方法建立一个空对象。

1五、过分耦合的消息链

  • Message Chains
  • 一个对象请求一个对象,而后后者请求另外一个对象,等等
  • 使用隐藏委托。

1六、中间人

  • Middle Man
  • 对象的基本特征之一就是封装,对外部世界隐藏内部细节,封装每每伴随着委托,但有可能过分使用委托,移除中间人

1七、狎昵关系

  • Inappropriate Intimacy
  • 两个类过于亲密,移动方法和字段让他们划清界限。若是划清不了就使用提炼类让他们融为一体吧

1八、殊途同归类

  • Alternative Classes with Different Interfaces
  • 重命名方法,提炼子类

1九、不完美的库类

  • Incomplete Library Class
  • 给库类加入新的方法,外部方法和本地扩展。

20、纯稚的数据类

  • Data Class
  • 不会说话的数据容器必定被其余类过度的操控着,运用封装字段封装,移动设置方法,移动方法,提炼方法。

2一、被拒绝的遗赠

  • Refused Bequest
  • 子类不肯所有继承,为这个子类建立一个兄弟类,在运用下移方法和字段把用不到的函数下推给那个兄弟,这样一来,超类就只持有全部子类共享的东西。
  • 用委托替换继承

2二、过多注释

  • Comments
  • 提炼方法。

1 从新组织函数

对函数的重构方法程序员

一、提炼函数

  • ExtractMethod
    • 1572396877904
  • 动机
    • 每一个函数的颗粒度都比较小,高层函数读起来就像是注释
    • 颗粒度比较小覆写也比较容易
  • 何时须要提炼函数
    • 当函数体的语义与函数名称偏离的时候就须要提取
  • 怎么提取
    • 将代码提取出来用函数的意图来命名(作什么)
    • 若是该代码段中有读取或改变临时变量
      • 该临时变量在原函数中有没有使用,
        • 优先考虑用查询取代临时变量
        • 没有直接将临时变量的声明移植到函数体中
        • 在函数体以前使用,做为参数传入
        • 在函数体以后使用,做为函数返回值返回
        • 以前以后都使用,做为参数传入,在做为返回值返回
    • 若是临时变量很是多,
      • 须要考虑这个函数体是否真的属于这个类
      • 查询替代临时变量

二、内联函数

  • Inline Method
    • 1572396856168
  • 何时须要内联
    • 当函数的本体和名称一样清楚易懂的时候
    • 当有一大群组织不太合理的函数,想重构的时候,将一大群函数内联而后从新提取
    • 有太多的间接层,全部函数彷佛都是对另外一个函数的简单委托
  • 怎么内联
    • 检查函数,肯定它不具备多态。
    • 找出该函数的全部引用点,用函数体替换(最好用文本查找的方式找)

三、内联临时变量

  • Inline Temp
    • 1572396842515
  • 动机
  • 何时作
    • 有一个临时变量,只被简单表达式赋值一次,而它妨碍其余重构手法
  • 怎么作

四、以查询取代临时变量*

  • Replace Temp with Query
    • 1572396817819
  • 动机
    • 临时变量是暂时的,若是这个临时变量须要被使用屡次就考虑须要用查询取代,这边的查询能够直接使用.net中的属性。
    • 临时变量会驱使函数变长,若是变成查询,类中的其余成员也能够访问。
  • 何时须要查询取代
    • 用一个临时变量保存其某一表达式的运算结果,须要一个查询函数取代临时变量
  • 怎么取代
    • 须要分解临时变量(临时变量被赋值超过一次),以查询取代临时变量,而后再替换临时变量
    • 首先应该将查询设置为私有的,当往后须要的时候再开放保护。
    • 不用考虑细微的性能问题,由于首先须要良好的架构才能使得程序正常运行。而后再考虑性能问题。

五、引入解释性变量

  • Introduce Explaining Variable
    • 在引入解释性变量以后,可使用导出方法或者用查询取代临时变量将临时变量替换掉。
    • 1572396792964
  • 动机
    • 使得复杂表达式能够阅读和管理
  • 何时须要引入
    • 有一个复杂的表达式
  • 怎么引入
    • 讲一个复杂表达式(或一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途
  • 与提炼函数的区别
    • 再提炼函数须要花费更大的工做量的时候

六、分解临时变量

  • Split Temporary Variable
    • 1572396764394
  • 动机
    • 若是一个临时变量承担太多的职责,会使得阅读者糊涂
  • 何时分解
    • 程序中有某个临时变量被赋值超过一次,它既不是循环变量也不是收集计算结果。
  • 怎么分解
    • 修改临时变量的名称并声明为常量

七、移除对参数的赋值*

  • Remove Assignments to Parameters
    • 这边的是针对函数参数体成员
    • 对参数的赋值的想法是比较危险的,一旦为参数进行赋值若是混淆值类型和引用类型很是容易产生不易察觉的错误。
    • 1572396741479
  • 动机
    • 由于面向对象的方式,因此数值类型的改变并不会改变原来传入的值,可是引用类型就会变化
    • 致使混用按值传递和按引用传递
  • 何时移除
    • 代码对函数的一个参数进行赋值时
  • 怎么移除
    • 经过创建一个临时变量,对临时变量进行修改,而后返回临时变量。
    • 若是须要返回一大堆函数,能够将返回的一大堆函数变成一个单一的对象,或者为每一个返回值设置一个独立函数。
    • 还能够在函数的每一个参数中增长一个const,这个方法只是在函数体较长的时候才可使用。

八、以函数对象取代函数

  • Replace Method with Method Object
    • 1572396709204
  • 动机
    • 小型函数优美动人
  • 何时取代
    • 有一个大型函数,对其中的局部变量的使用没法采用提炼方法的手段
  • 怎么提取
    • 创建一个新类,将全部的局部变量变成字段,而后将原函数体中的逻辑变成方法。

九、替换算法

  • Substitute Algorithm
    • 1572396971680
  • 动机
    • 发现一个算法的效率更高的时候
  • 何时替换
    • 想法把某个算法换成另外一个更为清晰的算法

2 在对象之间搬移特性

在面向对象的设计中,决定把责任放在哪里。算法

先使用移动字段,在移动方法数据库

一、搬移函数

  • Move Method
    • 1572397619571
  • 动机
    • 一个类与另外一个类高度耦合,就会搬移函数,经过这种手段,可使得类更加简单。
  • 何时搬移
    • 有个函数与其所属类以外的另外一个类有更多的交流。
    • 当不能确定是否须要移动一个函数,须要继续观察其余函数,先移动其它函数就会使决定变得容易一些。
  • 怎么搬移
    • 检查全部字段,属性和函数,考虑是否应该被搬移
    • 在该函数最经常使用引用中创建一个有相似行为的新函数
    • 将旧函数变成一个单纯的委托函数,或是将旧函数彻底移除。
    • 有多个函数使用这个须要搬移的特性,应考虑使用该特性的全部函数被一块儿搬移。
    • 检查全部子类和超类,看看是否有该函数其余声明
    • 若是目标函数使用了源类中的特性,能够将源对象的引用看成参数(多个参数或则存在方法须要调用),传给新创建的目标函数。
    • 若是目标函数须要太多源类特性,就得进一步重构,会将目标函数分解并将其中一部分移回源类。

二、搬移字段

  • Move Field
    • 1572397877822
  • 动机
    • 随着项目类的增长和扩充,有一些字段放在原来的类中已经不太合适
  • 何时搬移
    • 某个字段在另外一个类中被更多的用到
  • 怎么搬移
    • 修改源字段的全部用户,令它们改用新字段
    • 决定如何在源对象中引用目标对象,方法,新建字段引用
    • 新类中自我封装SetValue, GetValue。

三、提炼类*?

  • Extract Class
    • 1572397904071
  • 动机
    • 将复合类的职责提炼出新的类
    • 或者须要将类的子类化,分解原来的类
  • 何时提炼
    • 某个类作了应该由两个类作的事
  • 怎么提炼
    • 创建一个新类,将相关的字段和函数从旧类搬移到新类
    • 有可能须要一个双向链接, 可是在真正须要它以前,不要创建重新类往旧类的链接,若是创建起双向链接,检查是否能够将它改成单向链接。

四、将类内联化

  • Inline Class
    • 1572400836706
  • 动机
    • 一个类再也不承担足够责任,再也不由单独存在的理由。
  • 何时内联
    • 某个类没有作太多的事情
  • 怎么内联
    • 将这个类是多有特性搬移到另外一个类中,而后移除原类
    • 修改全部源类引用点,改而引用目标类

五、隐藏“委托关系”

  • Hide Delegate编程

    • 局限性是每当客户要使用受托类的新特性时,就必须在服务段添加一个简单委托函数,受托类的特性愈来愈多,这一过程会愈来愈痛苦。设计模式

    • 1572400926241

    • 简单委托关系1572400978816数组

  • 动机session

    • 封装意味着每一个对象都应该尽量少的了解系统的其余部分,
    • 若是客户调用对象字段获得另外一个对象,而后再调用后者的函数,那么客户就必须知道这一层关系。将委托关系隐藏起来不会波及客户。
  • 何时隐藏多线程

    • 客户经过一个委托类来调用另外一个对象
  • 怎么隐藏架构

    • 在服务类上创建客户所需的全部函数,用以隐藏委托关系
    • 1572253642885
    • manager=john.getDepartment().getManager();隐藏=>manager=john.getManager();隐藏了调用关系。

六、移除中间人

  • Remove Middle Man
    • 与隐藏委托关系相反
    • 1572401291820
  • 动机
    • 针对隐藏委托的局限性,当委托的方法愈来愈多时,服务类就彻底变成一个中间人,此时应该让客户直接调用受托类。
  • 何时移除
    • 某个类作了过多的简单委托动做
  • 怎么移除
    • 让客户直接调用受托类

七、引入外加函数

  • Introduce Foreign Method
    • 1572401389300
  • 动机
    • 发现一个好用的工具类不能修改工具类,添加方法
    • 但外加函数终归是权益之计,
  • 何时须要引入外加函数
    • 须要为提供服务的类增长一个函数,但没法修改这个类。
  • 怎么引入
    • 在客户类中创建一个函数,并以第一参数形式传入一个服务类实例

八、引入本地扩展

  • Introduce Local Extension
    • 1572401403718
  • 动机
    • 在不能修改的类中添加方法,方法的数量超过2个的时候外加函数难以控制,须要将函数组织到一块儿,经过两种标准对象技术:子类化和包装,子类化和包装叫作本地扩展。
    • 在子类化和包装中优先选择子类,
    • 使用包装会形成A=B,B不等于A的逻辑,子类等于包装类,包装类不等于子类
  • 何时引入
    • 须要为服务类提供一些额外函数,但没法修改类。
  • 怎么引入
    • 创建一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或包装类。
    • 子类化方案,转型构造函数应该调用适当的超类构造函数
    • 包装类方案,转型构造函数应该传入参数以实例变量的形式保存起来,用做接受委托的原对象。

3 从新组织数据

对于这个类的任何修改都应该经过该类的方法。类拥有一些数据却无所觉,拥有一些依赖无所觉是很是危险的。因此才要封装字段,封装集合,监视数据,用对象替代数组,用对象替代集合,关联改动。app

一、自封装字段

  • Self Encapsulate
    • 1572401773280
  • 动机
    • 直接访问变量的好处:子类能够经过覆写一个函数而改变获取数据的途径,它还支持更灵活的数据管理方式,如延迟初始化等,
    • 直接访问变量的好处:代码比较容易阅读,
    • 优先选择直接访问的方式,直到这种访问方式带来麻烦位置。
  • 何时须要自封装字段
    • 直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。
  • 怎么自封装
    • 为这个字段创建取值/设值函数,而且只以这些函数来访问字段。

二、以对象取代数据值

  • Replace Data Value with Object
    • 1572402017495
  • 动机
    • 简单数据再也不简单,
    • 注意:原来的数据值是值对象,改为对象可能变成引用类型,这样面临的问题是多个实例就不是同一个对象。须要用将引用对象改为值对象方法,
  • 何时须要对象取代
    • 有一个数据项,须要与其余数据和行为一块儿使用才有意义。
  • 怎么对象取代
    • 为替换值新建一个新类,其中声明final字段,修改原字段的引用,都修改成对象。

三、将值对象改为引用对象

  • Change Value to Reference
    • 对于值类型来讲,equals和==的功能是相等的都是比较变量的值、
    • 对于引用类型来讲,==是b比较两个引用是否相等,equals是比较的引用类型的内容是否相等,而使用equals是须要重写的,否则就是调用object中的equals
    • 1572402839811
  • 动机
    • 值对象通常是基本数据类型,并不在乎是否有副本的存在,
    • 引用对象是否相等,直接使用==操做符
  • 何时改引用
    • 一个类衍生出许多彼此相等的实例,但愿将它们替换为同一个对象
    • 类的每一个实例中的字段都是独立,就是值类型,每一个实例都对应一个字段对象。
    • 引用类型多个实例能够共用一个字段对象。不是全部
  • 怎么改
    • 建立简单工厂和注册表,工厂负责生产字段对象,注册表负责保存全部的字段对象
    • 类实例经过工厂请求字段实例,工厂经过访问注册表返回字段实例引用。
  • 例子
    • 1572403867513
    • 1572403915291
    • 1572403943801
    • 目前为止customer对象仍是值对象,即便多个订单属于同一客户但每一个order对象仍是拥有本身的customer对象。
    • 使用工厂方法替代构造函数
    • 1572404251004
    • 1572404268577
    • 1572404275823
    • 此时值对象才变成引用对象,多个实例间都共享同一个引用对象

四、将引用对象改为值对象

  • Change Reference to value
    • 这边引用对象改为值对象并非说须要把引用类型改为基本类型,而是即便引用类型是不一样副本,那么相同内容的引用内容也是相等(重写Equals())
    • 1572404484481
  • 动机
    • 若是引用对象开始变得难以使用,或许就应该将它改为值对象。
    • 引用对象必须被某种方式控制,并且必须向其控制者请求适当的引用对象,会形成区域之间错综复杂的关联。
    • 值对象应该是不可变的(不管什么时候,调用同一个对象的同一个查询函数都应该获得相同的结果),若是须要改变就须要从新建立一个所属类的实例,而不是在现有对象上修改。
  • 何时更改
    • 有一个引用对象,很小且不可变,并且不易管理。
  • 怎么更改
    • 检查重构目标是否为不可变对象,创建equals和hashcode方法
    • new Currency("USD").equals(new Currency("USD"));返回false。重写equal和hashcode使其返回true,这样对象就是值对象,不可变。

五、以对象取代数组

  • Replace Array with Object
    • 1572405339305
  • 动机
    • 数组是常见的组织数据的结构,只用于以某种顺序容纳一组类似对象。
  • 何时须要取代
    • 有一个数组,其中的元素各自表明不一样的东西
  • 怎么取代
    • 将数组的每一个不一样意思都抽象称字段

六、复制被监视的数据

  • Duplicate Observed Data
    • 1572405469613
  • 动机
    • 一个分层良好的系统,用户界面和处理业务逻辑的代码分开
    • MVC模式
  • 何时须要复制
    • 有一些领域数据置身于GUI控件中,而邻域函数须要访问这些数据
  • 怎么复制
    • 将该数据复制到一个领域对象中,创建一个Observer模式,用以同步领域对象和GUI对象内的重复数据

七、将单向关联改为双向关联

  • Change Unidirectional Association to Bidirectional

    • 有点像观察者模式,控制者是订阅端,被控制者是主题,主题存在辅助函数,用于修改反向指针,订阅端调用辅助函数来修改反向指针。
    • 1572405656193
  • 动机

    • 随着项目时间的推移须要双向关联
  • 何时改动

    • 两个类都须要使用对方特性,但其间中有一条单向链接
  • 怎么实现

    • 添加一个反向指针,并使修改函数可以同时更新两条链接。

    • 在被引用的类中增长一个字段,保存反向指针。

    • 控制端和被控制端

      • 一对多的关系,可使用单一引用的一方(就是多的那一方)承担控制者的角色。
      • 对象是组成另外一对象的部件,该部件负责控制关联关系
      • 若是二者都是引用对象,多对多,那么无所谓。
    • 被控端创建一个辅助函数负责修改反向指针

    • 若是既有的修改函数在控制端,让它负责控制修改反向指针

    • 若是既有的修改函数在被控端,就在控制端创建一个控制函数,并让既有的修改函数调用这个新建的控制函数,来控制修改反向指针。

八、将双向关联改成单向关联

  • Change Bidirectional Association to Unidirectional
    • 1572406648238
  • 动机
    • 双向关联必需要符出代价,维护双向关联,确保对象被正确建立和删除而增长的复杂度。
    • 双向关联还会形成僵尸对象,某个对象已经死亡却保留在系统中,由于它的引用尚未彻底清楚。
    • 双向关联也会迫使两个类之间有了依赖,对其中任一个类的修改,均可能引起另外一个类的变化。
  • 何时须要
    • 两个类之间有双向关联,但其中一个类再也不须要另外一个的特性
  • 怎么修改
    • 去除没必要要的关联
    • 将私有字段去掉,须要依赖的函数,将依赖类做为参数传入,而后调用。
    • 建立一个静态字典保存全部的依赖类,经过取值函数来得到字段遍历对比依赖的引用是否相同来获取依赖类。

九、以字面常量取代魔法数

  • Replace Magic Number with Symbolic Constant
    • 1572406815589
  • 动机
  • 何时取代
    • 有一个字面数值,并带有特别含义
  • 怎么取代
    • 创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。

十、封装字段

  • Encapsulate Field
    • 1572406832493
  • 动机
    • 数据声明为public被看作一种很差的作法,会下降模块化程度。
    • 拥有该数据对象却毫无察觉,不是一件好事
  • 何时封装
    • 类中存在一个public字段
  • 怎么封装
    • 将原字段声明为private,并提供相应的访问函数

十一、封装集合

  • Encapsulate Collection
    • 除非经过封装的集合类,否则没有任何实例可以修改这个集合。
    • 1572406851542
  • 动机
    • 在一个类中使用集合并将集合给取值函数,但类不该该返回集合自身,由于这回让用户得以修改集合内容而对集合的使用者一无所知。
    • 不该该为集合提供一个设值函数,但应该为集合添加/移除元素的函数,这样集合的拥有者就能够控制集合元素的添加和移除。
  • 何时封装
    • 有一个函数返回一个集合
  • 怎么封装
    • 让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数

十二、以数据类取代记录

  • Replace Record with Data Class
  • 动机
    • 从数据库读取的记录,须要一个接口类,用来处理这些外来数据。
  • 何时作
    • 须要面对传统编程环境中的记录结构
  • 怎么作
    • 为该记录建立一个哑数据对象。
    • 新建一个类,对于记录汇总的每一项数据,在新建的类中创建一个对应的private字段,并提供相应的取值和设值函数。

1三、以类取代类型码

  • Replace Type Code with Class
    • 原来的类型码多是int类型,创建一个类型码的类,全部的int转换成类型码的类,其实有点像建立一个枚举类型,而后用枚举类型取代int。
    • 1572411176016
  • 动机
    • 类型码或枚举值很常见,但终究只是一个数值,若是是一个类就会进行类型检验,还能够为这个类提供工厂函数,保证只有合法的实例才会被建立出来。
    • 若是有switch必须使用类型码,但任何switch都应该使用多态取代条件去掉。为了进行这样的重构还须要使用子类取代类型码,用状态或策略替换类型码。
  • 何时作
    • 类之中有一个数值类型码,但它并不影响类的行为
  • 怎么作
    • 以一个新的类替换该数值类型码
    • 用以记录类型码的字段,其类型应该和类型码相同,还应该有对应的取值函数,还应该用一组静态变量保存容许被建立的实例,并以一个静态函数根据本来的类型码返回合适的实例。

1四、以子类取代类型码

  • Replace Type Code with Subclasses
    • 1572486570053
  • 动机
  • 何时作
    • 有一个不可变的类型码,它会影响类的行为
    • 若是类型码会影响宿主类的行为,最好的作好就是用多态来处理变化行为。就是switch和if else结构。
    • 类型码值在对象船舰以后发生变化,类型码宿主类已经拥有子类,这两种状况下就须要使用状态/策略设计模式
  • 怎么作
    • 以子类取代这个类型码

1五、以State/Strategy取代类型码

  • Replace Type Code with State/Strategy
    • 每一个状态有特定的数据和动做。
    • 1572486550695
  • 动机
  • 何时作
    • 有一个类型码,它会影响类的行为,但没法经过继承手法消除它
  • 怎么作

1六、以字段取代子类

  • Replace Subclass with Fields
    • 1572486531862
  • 动机
  • 何时作
    • 各个子类的惟一差异只在返回常量数据的函数身上
    • 直接用该字段的不一样值表示子类就能够了。
  • 怎么作
    • 修改这些函数,使它们返回超类中某个(新增字段,而后销毁子类)

4 简化条件表达式

一、分解条件表达式

  • Decompose Conditional
    • 1572486513216
  • 动机
    • 复杂的条件逻辑是最常致使复杂度上升的地点之一,
  • 何时作
    • 有一个复杂的条件语句
  • 怎么作
    • 从if,then,else三个段落中分别提炼出独立函数
    • 将其分解为多个独立函数,根据每一个小块代码的用途分解而得的新函数命名。
    • 不少人都不肯意去提炼分支条件,由于这些条件很是短,可是提炼以后函数的可读性很强,就像一段注释同样清楚明白。

二、合并条件表达式

  • Consolidate Conditional Expression
    • 其实就是用一个小型函数封装一下,小型函数的名字能够做为注释。
    • 1572486838506
  • 动机
    • 合并后的条件代码会使得检查的用意更加清晰,合并前和合并后的代码有着相同的效果。
  • 何时作
    • 有一系列条件测试,都获得相同结果
  • 怎么作
    • 将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。

三、合并重复的条件片断

  • Consolidate Duplicate Conditional Fragments
    • 1572487145359
  • 动机
  • 何时作
    • 在条件表达式的每一个分支上都有着相同的一段代码
  • 怎么作
    • 将这段重复代码搬移到条件表达式以外。

四、移除控制标记

  • Remove Control Flag
  • 动机
    • 单一出口原则会迫使让妈中加入讨厌的控制标记,大大下降条件表达式的可读性,
  • 何时作
    • 在一系列布尔表达式中,某个变量带有"控制标记"(control flag)的做用
  • 怎么作
    • break语句或return语句取代控制标记

五、以卫语句取代嵌套条件表达式

  • Replace Nested Conditional with Guard Clauses
    • 1572487743463
  • 动机
    • 单一出口的规则其实并非那么有用,保持代码清晰才是最关键的。
  • 何时作
    • 函数中条件逻辑令人难以看清正常的执行路径
  • 怎么作
    • 使用卫语句表现全部特殊状况。

六、以多态取代条件表达式

  • Replace Conditional with Polymorphism
    • 1572488390812
  • 动机
    • 若是须要根据对象的不一样类型而采起不一样的行为,多态使你没必要编写明显的条件表达式。
    • 同一组条件表达在程序许多地点出现,那么使用多态的收益是最大的。
  • 何时作
    • 有一个条件表达式,根据对象类型的不一样而选择不一样的行为
  • 怎么作
    • 将这个体哦阿健表示式的每一个分支放进一个子类的覆写函数中,而后将原始函数声明为抽象函数。

七、引入Null对象

  • Introduce Null Object
    • 1572488884565
  • 动机
    • 多态的最根本好处就是没必要要想对象询问你是什么类型然后根据获得的答案调用对象的某个行为,只管调用该行为就是了。
    • 空对象必定是常量,它们的任何成分都不会发生变化,所以可使用单例模式来实现它们。
  • 何时作
    • 须要再三检查对象是否为Null
  • 怎么作
    • 将null对象替换成null对象。

八、引入断言

  • Introduce Assertion
    • 1572489734260
  • 动机
    • 断言是一个条件表达式,应该老是为真,若是它失败,表示程序员犯了一个错误。所以断言的失败应该致使一个非受控异常(unchecked exception)。
    • 加入断言永远不会影响程序的行为。
    • 用它来检查必定必须为真的条件。
  • 何时作
    • 某一段代码须要对程序状态作出某种假设
  • 怎么作
    • 以断言明确表现这种假设

5 简化函数调用

全部的数据都应该隐藏起来。

一、函数更名

  • Rename Method
    • 1572491178470
  • 动机
    • 将复杂的处理过程分解成小函数。
  • 何时作
    • 函数名称未能揭示函数的用途
  • 怎么作
    • 修改函数名称

二、添加参数

  • Add Parameter
    • 1572491269669
  • 动机
  • 何时作
    • 某个函数须要从调用端获得更多信息
    • 在添加参数外经常还有其余的选择,只要有可能,其余选择都比添加参数要好(查询),由于它们不会增长参数列的长度,过长的参数列是一个很差的味道。
  • 怎么作
    • 为此函数添加一个对象参数,让该对象带进函数所需信息。

三、移除参数

  • Remove Parameter
    • 1572491400021
  • 动机
    • 可能常常添加参数却不多去除参数,由于多余的参数不会引发任何问题,相反之后可能还会用到它。请去除这些想法。
  • 何时作
    • 函数本体不须要某个参数
  • 怎么作
    • 将该参数去除。

四、将查询函数和修改函数分离

  • Separate Query from Modifier
    • 1572491591701
  • 动机
    • 在多线程系统中,查询和修改函数应该被声明为synchronized(已同步化)
  • 何时作
    • 某个函数既返回对象状态值,又修改对象状态
    • 任何有返回值的函数,都不该该又看获得的反作用。
    • 常见的优化是将某个查询结果放到某个字段或集合中,后面如何查询,老是得到相同的结果。
  • 怎么作
    • 创建两个不一样的函数,其中一个负责查询,另外一个负责修改。

五、令函数携带参数

  • Parameterize
    • 1572567550304
  • 动机
    • 去除重复代码
  • 何时作
    • 若干函数作了相似的工做,但在函数本体中却饱含了不一样的值
  • 怎么作
    • 创建单一函数,以参数表达那些不一样的值

六、以明确函数取代参数

  • Replace Parameter with Explicit Methods
    • 1572567656198
  • 动机
    • 避免出现条件表达式,接口更清楚,编译期间就能够检查,
    • 若是在同一个函数中,参数是否合法还须要考虑
    • 可是参数值不会对函数的行为有太多影响的话就不该该使用本项重构,若是须要条件判断的行为,能够考虑使用多态。
  • 何时作
    • 有一个函数,其中彻底取决于参数值不一样而采起不一样行为
  • 怎么作
    • 针对该参数的每个可能值,创建一个独立函数

七、保持对象完整

  • Preserve While Object
    • 1572568707669
  • 动机
    • 不适用完整对象会形成重复代码
    • 事物都是有两面性,若是你传的是数值,被调用函数就只依赖于这些数值,若是传的是对象,就要依赖于整个对象。若是依赖对象会形成结构恶化。那么就不该该使用保持对象完整。
    • 若是这个函数使用了另外一个对象的多项数据,这可能觉得着这个函数实际上应该定义在那些数据所属的对象上,应该考虑移动方法。
  • 何时作
    • 从某个对象中取出若干值,将它们做为某一次函数调用时的参数
  • 怎么作
    • 改成传递整个对象

八、以函数取代参数

  • Replace Parameter with Methods
    • 1572569273989
  • 动机
    • 尽量缩减参数长度
  • 何时作
    • 对象调用某个函数,并将全部结果做为参数传递给另外一个函数,而接受该参数的函数本省也可以调用前一个函数。
  • 怎么作
    • 让参数接受者去除该项参数,并直接调用前一个函数。

九、引入参数对象

  • Introduce Parameter Object
    • 1572569919231
  • 动机
    • 特定的一组参数老是一块儿被传递,可能有好几个函数都使用这一组参数,这些函数可能隶属于同一个类,也可能隶属于不一样的类。这样的参数就是所谓的数据泥团,能够运用一个对象包装全部的这些数据,再以该对象取代它们。
  • 何时作
    • 某些参数老是很天然地同时出现
  • 怎么作
    • 以一个对象取代这些参数

十、移除设值函数

  • Remove Setting Method
  • 动机
    • 使用了设值函数就暗示了这个字段值能够被改变。
  • 何时作
    • 类中某个字段应该在对象建立时被设值,而后就再也不改变。
  • 怎么作
    • 去掉该字段的全部设值函数。

十一、隐藏函数

  • Hide Method
    • 1572570692493
  • 动机
    • 面对一个过于丰富、提供了过多行为的接口时,就值得将非必要的取值函数和设置函数隐藏起来
  • 何时作
    • 有一个函数,历来没有被其余任何类用到
  • 怎么作
    • 将这个函数修改成private

十二、以工厂函数取代构造函数

  • Replace Constructor with Factory Method
    • 1572570846559
  • 动机
    • 使用以工厂函数取代构造函数最显而易见的动机就是在派生子类的过程当中以工厂函数取代类型码
    • 工厂函数也是将值替换成引用的方法。
  • 何时作
    • 但愿在建立对象时不只仅是作简单的构建动做
  • 怎么作
    • 将构造函数替换为工厂函数
    • 使用工厂模式就使得超类必须知晓子类,若是想避免这个能够用操盘手模式,为工厂类提供一个会话层,提供对工厂类的集合对工厂类进行控制。

1三、封装向下转型

  • Encapsulate Downcast
    • 1572572975294
  • 动机
    • 能不向下转型就不要向下转型,但若是须要向下转型就必须在该函数中向下转型。
  • 何时作
    • 某个函数返回对象,须要由函数调用者执行 向下转型
  • 怎么作
    • 将向下转型动做移到函数中

1四、以异常取代错误码

  • Replace Error Code with Exception
    • 1572573309472
  • 动机
    • 代码能够理解应该是咱们虔诚最求的目标。
  • 何时作
    • 某个函数返回一个特定的代码,用以表示某种错误状况
  • 怎么作
    • 改用异常
    • 决定应该抛出受控(checked)异常仍是非受控(unchecked)异常
      • 若是调用者有责任在调用前检查必要状态,就抛出非受控异常
      • 若是想抛出受控异常,能够新建一个异常类,也可使用现有的异常类。
    • 找到该函数的全部调用者,对它们进行相应调整。
      • 若是函数抛出非受控异常,那么就调整调用者,使其在调用函数前作适当检查,
      • 若是函数抛出受控异常,那么就调整调用者,使其在try区段中调用该函数。

1五、以测试取代异常

  • Replace Exception with Test
    • 1572576674845
  • 动机
    • 在异常被滥用的时候
  • 何时作
    • 面对一个调用者能够预先检查的体哦阿健,你抛出一个异常
  • 怎么作
    • 修改调用者,使它在调用函数以前先作检查

6 处理继承关系

一、字段上移

  • Pull Up Field
    • 1572577009547
  • 动机
    • 减小重复
  • 何时作
    • 两个子类拥有相同的字段
  • 怎么作
    • 将该字段移至超类

二、函数上移

  • Pull Up Method
    • 1572577175581
  • 动机
    • 滋生错误
    • 避免重复
  • 何时作
    • 有些函数在各个子类中产生彻底相同的结果
  • 怎么作
    • 将该函数移至超类
    • 最烦的一点就是,被提高的函数可能会引用子类中出现的特性,若是被引用的是一个函数能够将这个函数一同提高至超类,或则在超类中创建一个抽象函数。
    • 若是两个函数类似但不相同,能够先借助塑造模板函数。

三、构造函数本体上移

  • Pull Up Constructor Body
    • 1572577601688
  • 引用
    • 若是重构过程过于复杂,能够考虑使用工厂方法。
  • 何时作
    • 在各个子类中拥有一些构造函数,它们的本体机会彻底一致
  • 怎么作
    • 在超类中新建一个构造函数,并在子类构造函数中调用它。

四、函数下移

  • Push Down Method
    • 1572577790347
  • 动机
    • 把某些行为从超类移动到特定的子类中。
  • 何时作
    • 超类中某个函数只与部分子类有关
  • 怎么作
    • 将这个函数移到相关的那些子类中
    • 若是移动的函数须要使用超类中的某个字段,则须要将超类中的字段的开放protected.

五、字段下移

  • Push Down Field
    • 1572577916400
  • 动机
  • 何时作
    • 超类中的某个字段只被部分子类用到
  • 怎么作
    • 将这个字段移到须要它的那些子类去

六、提炼子类*?

  • Extract Subclass
    • 1572578034339
  • 动机
    • 类中的某些行为只被一部分实例用到,其余实例不须要,有时候这些行为上的差别是经过类型码分区的,可使用子类替换类型码,或则使用状态或策略模式替代类型码。
    • 抽象类和抽象子类则是委托和继承之间的抉择
    • 抽象子类会更加容易,可是一旦对象创建完成,没法再改变与类型相关的行为。
  • 何时作
    • 类中的某些特性只被某些实例用到
  • 怎么作
    • 新建一个子类,将上面所说的那一部分特性移到子类中
    • 为源类定一个新的子类
    • 为这个新的子类提供构造函数
      • 让子类构造函数接受与超类构造函数相同的参数,并经过super调用超类的构造函数。
      • 用工厂替换构造函数
    • 找出调用结果超类构造函数的全部地点,新建子类
    • 下移方法和字段

七、提炼超类*?

  • Extract Superclass

    • 1572578336169
  • 动机

  • 何时作

    • 两个类有类似特性
  • 怎么作

    • 为这两个类创建一个超类,将相同特性移至超类。
  • 新建一个空白抽象类

    • 上移字段和方法
      • 先搬移字段
      • 子类函数中有相同的签名,但函数体不一样,能够抽象函数
      • 若是方法中有相同算法,可使用提炼算法,将其封装到同一个函数中。

    八、提炼接口

    • Extract Interface
      • 1572579063782
    • 动机
      • 类之间彼此互用的方式有若干种,某一种客户只使用类责任区的一个特定子集。
      • 某个类在不一样环境下扮演大相径庭的角色,使用接口就是一个好主意。
    • 何时作
      • 若干客户使用类接口中同一个子集,或者两个类的接口有部分相同
    • 怎么作
      • 将相同的子类提炼到一个独立接口中。

九、折叠继承关系

  • Collapse Hierarchy
    • 1572579987774
  • 动机
  • 何时作
    • 超类和子类之间无太大区别
  • 怎么作
    • 将它们合为一体

十、塑造模板函数

  • Form Template Method
    • 1572580131316
  • 动机
    • 既避免重复也保持差别。
  • 何时作
    • 有一些子类,其中相应的某些函数以相同顺序执行相似的操做,但各个操做的细节上有所不一样。
  • 怎么作
    • 将这些操做分别放进独立函数中,并保持它们都有相同的签名,因而原函数也就变得相同的,而后将原函数上移至超类

十一、以委托取代继承

  • Replace Inheritance with Delegation
    • 1572580380411
  • 动机
    • 超类中有许多操做并不真正适用于子类,这种状况下,你所拥有的接口并未真正反映出子类的功能。
  • 何时作
    • 某个子类只使用超类接口中的一部分,或是根本不须要继承而来的数据
  • 怎么作
    • 在子类中新建一个字段用以保存超类,调整子类函数,令它改而委托超类,而后去掉二者之间的继承关系。
    • 在子类中新建一个字段,使其引用超类的实例
    • 修改子类中的全部函数,让它们再也不使用超类,转而使用上述那个受托字段。

十二、以继承取代委托

  • Replace Delegation with Inheritance
    • 1572583811463
  • 动机
    • 若是并无使用受托类的全部函数,就不该该使用用继承替换委托,
    • 可使用去除中间层的方法让客户端本身调用受托函数。
  • 何时作
    • 在两个类之间使用委托关系,并常常为整个接口编写许多极简单的委托函数。
  • 怎么作

7 大型重构

一、梳理并分解继承体系

  • Tease Apart Inheritance
    • 就是让每一个类的职责更明确更单一,当一个类的职责混乱时,经过绘制职责图来分离职责,并建立另外一个超类,将相关的字段和方法都移动到另外一个超类
    • 1572653896760
  • 动机
    • 混乱的继承体系是一个严重的问题,会致使重复代码,然后者正是程序员生涯的致命毒药。还会使修改变得困难,由于特定问题的解决决策被坟山到了整个继承体系。
  • 何时作
    • 某个继承体系同时承担两项责任
  • 怎么作
    • 创建两个继承体系,并经过委托关系让其中一个能够调用另外一个
    • 首先识别出继承体系所承担的不一样责任,而后创建一个二维表格(或则三位乃至四维表格),并以坐标轴标示不一样的任务,
    • 判断哪一项责任更重一些,并准备将它留在当前的继承体系中,准备将另外一项责任移到另外一个继承体系中。
    • 使用抽象类方法从当前的超类提炼出一个新类,用以表示重要性稍低的责任,并在原超类中添加一个实例变量,用以保存新类的实例。
    • 对应于原继承体系中的每一个子类,建立上述新类的一个子类,在原继承体系的子类中,将前一步骤所添加的实例变量初始化为新建子类的实例。
    • 针对原继承体系中的每一个子类,使用搬移函数的方法迁移到与之对应的子类中。
    • 1572658453762
    • 1572658467189
    • 1572658478819
    • 1572658495471
    • 1572658514062

二、将过程化设计转化为对象设计

  • Convert Procedural Design to Objects
    • 1572659942645
  • 动机
  • 何时作
    • 有一些传统过程化风格的代码
  • 怎么作
    • 将数据记录变成对象,将大块的行为分为小块,并将行为移入相关对象之中。
    • 针对每个记录类型,将其转变为只含访问函数的哑数据对象
    • 针对每一处过程化风格,将该出的代码提炼到一个独立类中。
    • 针对每一段长长的程序,试试提炼方法将长方法分解并将分解后的方法移动到相关的哑数据类。

三、将领域和表诉/显示分离

  • Separate Domain from Presentation

    • 1572660551940
  • 动机

    • MVC模式最核心的价值在于,它将用户界面代码(即视图:亦即现今常说的展示层)和领域逻辑(即模型)分离了,展示类只含用以处理用户界面的逻辑:领域类包含任何与程序外观相关的代码,只含业务逻辑相关代码,将程序中这两块复杂的部分加以分离,程序将来的修改将变得更加容易,同时也使用赞成业务逻辑的多种展示方式称为可能。
  • 何时作

    • 某些GUI类中包含了领域逻辑
  • 怎么作

    • 将领域逻辑分离出来,为它们创建独立的邻域类。

    • 为每一个窗口创建一个领域类,

    • 若是窗口内含有一张表格,新建一个类来表示其中的行,再以窗口所对应之领域类中的一个集合来容纳全部行领域对象

    • 检查窗口中的数据,若是数据只被用于UI,就把它留着,若是数据被领域逻辑使用,并且不显示与窗口上,咱们就可使用移动方法将它搬移到领域类中,若是数据同时被UI和领域逻辑使用,就对它实施复制被监视数据,使它同时存在于两处,并保持两处之间的同步。

    • 展示类中的逻辑,实施提炼方法将展示逻辑从邻域逻辑中分开,一旦隔离了邻域逻辑,在运用搬移方法将它移到邻域类。

    • 1572661870682

    • 1572661971112

四、提炼继承体系

  • Extract Hierarchy
    • 1572662102757
  • 动机
    • 一开始设计者只想以一个类实现一个概念,但随着设计方案的演化,最后可能一个类实现两个三乃至十个不一样的概念。
  • 何时作
    • 有某个类作了太多的工做,其中一部分工做是以大量条件表达式完成的
  • 怎么作
    • 创建继承体系,以一个子类表示一种特殊状况。
    • 有两种重构的手法
      • 没法肯定哪些地方会发生变化
    • 不肯定哪些地方会发生变化
      • 鉴别出一中变化状况,
        • 若是这种拜年话可能在对象声明周期的不一样阶段而有不一样体现就用提炼方法将它提炼为一个独立的类
      • 针对这种变化状况,新建一个子类,并对原始类实施工厂方法替代构造函数,再次修改工厂方法,令它返回适当的子类实例。
      • 将含有条件逻辑的函数,一个个复制到子类
        • 有必要隔离函数中的条件逻辑和非条件逻辑。
      • 删除超类中那些被全部子类覆写的函数本体,并将它们声明为抽象函数。
    • 肯定原始类中每一种变化
      • 针对原始类中每一种变化状况,创建一个子类,
      • 使用工厂方法替代构造函数将原始类的构造函数转变成工厂函数,并令它针对每一种变化状况返回适当的子类实例。
        • 若是原始类中的各类变化状况是以类型码标示,使用子类替换类型码,若是那些变化状况在对象周期的不一样阶段会有不一样体现,使用状态和策略模式替换类型码
      • 针对带有条件逻辑的函数,实施用多态替换条件若是非整个函数的行为有所变化,请先运行提炼方法将变化部分和不变部分分隔开来
相关文章
相关标签/搜索