• 第5章 重构列表
  • 第6章 重新组织函数
  • 第8章 重新组织数据
  • 第9章 简化条件表达式 237
  • 第10章 简化函数调用
  • 第11章 处理概括关系 319

    第5章 重构列表

    5.1 重构的记录格式103

    这个格式可以作为自己未来记录重构手法的标准,方便查阅自己常用的和总结的重构方式:

    • 名称:重构词汇表
    • 概要:适用的情景,以及所做的事情
    • 动机:说明“为什么需要”和“什么情况不做”
    • 做法:重构的步骤(看似简单,其实很重要,因为重构不建议跳跃完成,最好保证每次一小步都是正确的)
    • 范例:通过例子正确的理解重构手法的操作方式

    5.2 寻找引用点105

    引用点:就是那些被重构的代码被哪些代码调用了。现在的重构工具已经可以简化这些操作,但是工具永远都是有限的,除了工具还应该熟悉一些基本的操作手段。

    5.3 这些重构手法有多成熟106

    学习和使用这些重构仅仅是个起点,业务在变、工具在变、语言在变、对事物的理解也在变,因此完全挺有自己的重构手段才是最终的目标。

    第6章 重新组织函数

    • Extract Method是本章的重点,用于解决Long Methods问题;
    • Extract Method的困难是处理局部变量,特别是局部变量中的临时变量,为此需要本章中的其他方法作为补充。

    6.1 (P110)Extract Method(提炼函数)

    动机:Long Methods问题或者代码需要Comments问题;
    方法:(可以使用IDE提供的重构工具,下面的具体操作的原理)

    • 创造一个新函数,根据这个函数的意图来对它命名;
    • 将提炼的代码拷贝到新建函数中;
    • 检查提炼的代码是否引用了“作用域限于原函数”的变量(局部变量、原函数参数)
    • 检查提炼的代码是否包含了“仅用于被提炼代码段”的临时变量
      • 如果有,在目标函数中将之声明为局部变量
    • 检查被提炼代码段,看看是否有任何局部变量的值被他改变,
      • 如果临时变量的值被修改了,尝试将提炼的代码变为一个查询,将结果返回给相关变量;
      • 如果不好操作,或者被修改的变量多于一个,可以尝试(分解临时变量/以查询替换变量)等手段了。
    • 将被提炼代码段中需要被读取的局部变量,当参数传给目标函数
    • 处理完所有局部变量后,编译,检查
    • 在源函数中,将被提炼的代码替换为对目标函数的调用
    • 编译,检查,测试

    6.2 Inline Method(内联函数)117

    动机:当函数内部的代码与其名称一样易懂,可以在函数调用点插入函数本体,移除该函数。
    补充:对有一群不太合理的函数,可以先内联为一个长函数,然后再提炼出合理的小函数

    6.3 Inline Temp(内联临时变量)119

    动机:当临时变量只被一个简单的表达式赋值一次,而且它妨碍其他重构方法时,才需要重构
    条件:Inline Temp多半是为Replace Temp with Query(以查询取代临时变量)准备
    方法:将所有对该变量的引用动作替代成对它赋值的表达式本身。

    6.4 Replace Temp with Query(以查询取代临时变量)120

    动机:你的程序以一个临时变量保存一个表达式的计算结果
    方法:将表达式提炼出独立的函数,然后临时变量的调用替换成新函数的调用。此后新函数也能被调用。
    具体方法: 将提炼出来的函数用private修饰,如果独立函数有副作用,那对它进行Separate Query from Modifier(将查询函数和修改函数分离)

    6.5 Introduce Explaining Variable(引入解释性变量)124

    将复杂表达式(或者其中一部分)的结果赋值给一个临时变量,用临时变量名称来解释表达式的用途

    6.6 Split Temporary Variable(分解临时变量)128

    临时变量被赋值超过一次,但是它既不是循环变量也不是被用于收集计算结果
    原因:一个变量应该承担一个责任,如果被赋值多次很可能承担了多个责任
    方法:针对每次赋值,创建新的临时变量

    6.7 Remove Assignments to Parameters(移除对参数的赋值)131

    java是值传递,对参数的任何修改都不会对调用端产生影响,所以对于用过引用传递的人可能会发生理解错误
    参数应该仅表示“被传递过来的东西”

    6.8 Replace Method with Method Object(以函数对象取代函数)135

    动机:在大型函数内,对局部变量的使用导致难以使用Extract Method(提炼函数)进行重构
    方法:将这个函数放入一个对象里,局部变量变成对象成员变量,然后可以在同一对象中将这个大型函数分解为多个小型函数。
    原因:局部变量会增加分解函数的困难度

    6.9 Substitute Algorithm(替换算法)139

    把某个算法替换成更清晰的方法(算法)。

    第7章 在对象之间搬移特性141

    本章的重点是搬移(Move)。“决定把责任放在哪里”是面向对象设计中最重要的事情之一,但是刚开始设计时,由于技术和业务知识的不足无法保证做出的决定是正确的,那么后期调整中将“责任”搬移到正确的对象中就是重要的重构手段。而Move Method(142)和Move Field(146)就是搬移“责任”过程中最基本的两个方法。

    7.1 Move Method(搬移函数)142

    动机:类中某个函数与其他类交互过多
    方法:将该函数搬移到交互最多的类里面,将旧函数变成委托函数或者删除。
    具体方法:

    • 检查源类中被源函数使用的一切特性,如果特性被其他函数使用,考虑这些函数一起搬移
    • 检查源类的子类和超类,看看是否有该函数的声明,如果出现,很可能不能搬移。
    • 目标类需要使用源类的特性:
      1. 将该特性转移到目标类;
      2. 建立目标类到源类之间引用。
      3. 将源类作为参数传给目标类
      4. 将该特性作为参数传给目标类
    • 如果源函数包含异常处理,需要考虑是在目标类还是源函数处理

    7.2 Move Field(搬移字段)146

    动机:类中某个字段被其他类频繁使用(包括:传参数、调用取值函数、调用设值函数)
    方法:将该字段搬移到目标类
    具体方法:

    • 先封装这个字段;
    • 在目标类建立这个字段,并且封装;
    • 设定目标对象;
    • 替换对源字段的引用为目标类的取值函数

    7.3 Extract Class(提炼类)149

    动机:一个类做了两个类的事
    方法:

    • 建立新类,将相应的字段和函数放到新类
    • 使用Move Field重构;
    • 使用Move Method重构;
    • 判断是否需要公开新类。

    7.4 Inline Class(将类内联化)154

    动机:某个类功能太少,与Extract Class(提炼类)相反 方法:将这个类的所有特性搬移到另一类中,移除该类。
    原因:多次Extract Class后,原类大部分功能被移走,将这个萎缩类与其他相近的类合并

    7.5 Hide Delegate(隐藏“委托关系”)157

    动机:客户端通过委托类来取得另一个对象的信息
    方法:在服务类上建立客户端所需数据的函数,然后隐藏委托关系
    依据:符合“封装”的特性。当委托类发生变化不会对客户端造成影响,减少客户端与调用者之间的耦合性。

    7.6 Remove Middle Man(移除中间人)160

    动机:某个类做了过多的委托动作
    方法:让客户端直接调用委托类,与Hide Delegate(隐藏“委托关系”)相反 依据:当原委托类的特性越来越多,服务类的委托函数将越来越长,需要让客户端直接调用,避免服务类沦为中间人。

    7.7 Introduce Foreign Method(引入外加函数)162

    动机:使用的类无法提供某个功能,但是又不能修改该类
    方法:新建函数,并将服务类的对象实例作为参数传入。
    具体动机:如果需要为服务类增加大量的方法,请考虑使用Introduce Local Extension(引入本地扩展)

    7.8 Introduce Local Extension(引入本地扩展)164

    动机:使用的类无法提供多个功能,但是又不能修改该类
    方法:建立新的类,在新类中建立需要的功能函数,可以作为服务类的子类实现新的类,也可以包装服务类实现新的类。
    具体情况:

    • 首选子类,工作量最小
      • 但是必须在对象创建期实施,如果不行就只能选择包装类;
      • 子类的对象不能修改父类的数据,否则建议选择包装类,因为会导致父类对象与子类对象的数据可能不一致
    • 包装类需要实现被包装对象的所有接口,工作量很大。

    第8章 重新组织数据

    本章重点是如何更好地封装各种类型的数据。最常用的手段就是Self Encapsulate Field(171)

    8.1 Self Encapsulate Field(自封装字段)171

    动机:直接访问一个字段,但是字段之间的耦合关系逐渐变得笨拙。
    方法:自封装就是在对于类内部的字段也封装一个设值取值的函数。
    争论:字段访问方式是直接访问还是间接访问一致争论不断
    间接访问的好处:

    • 子类可以通过覆盖一个函数来改变获取数据的途径;
    • 支持更灵活的数据管理,如延迟加载(需要用到才加载)等。直接访问的好处:代码容易读懂,理解不需要转换为取值函数。

    8.2 Replace Data Value with Object(以对象取代数据值)175

    动机:假如一个数据项需要与其他数据一起使用才有意义。数据已经不仅仅由一条简单的数据项组成,例如:电话号码
    方法:将数据变成对象。

    8.3 Change Value to Reference(将值对象改为引用对象)179

    动机:一个类有许多相等的实例,希望把这些相等的实例统一为一个对象,方便统一修改或者进行相等性比较
    方法:将值对象变成引用对象
    “引用对象”与“值对象”的区别:

    • 每个引用对象代表着现实中一个对象,使用对象的一致性用来检测两个对象是否相等,即(==)
    • 值对象完全由其自身的值来相互区分,需要重写一些方法用来检测两个对象是否相等。(重写equals()和hashcode()方法)具体方法:
    • 需要使用工厂模式来创建对象
    • 需要另一个对象(或者是自身)作为访问点来访问定义的引用对象,对象用Dictionary或者HashTable来保存对象
    • 决定对象是预先创建还是动态创建

    8.4 Change Reference to Value(将引用对象改为值对象)183

    动机:引用对象,很小且不可变,而且不易管理

    • 很小:创建许多也不会消耗太多内存
    • 不可变:不需要复杂的管理代码,也不需要考虑同步问题,还会造成别名问题具体方法:
    • 检查重构目标是否是不可变对象或者可修改成不可变对象
      • 使用Remove Setting Method变成不可变对象
      • 如果无法修改成不可变对象,就放弃重构
    • 重写hashCode和equals()方法
    • 取消使用的工厂模式,并将对象的构造函数设为public

    8.5 Replace Array with Object(以对象取代数组)186

    动机:如果数据存储的值代表不同的东西。
    方法:将数组变成对象,数组的每个元素用字段表示

    8.6 Duplicate Observed Data(复制“被监视数据”)189

    动机: 有业务数据置身于GUI控件中,而与业务相关的函数需要访问这些数据
    方法:将业务数据复制到业务类中。建立Observer模式,同步UI和业务类的数据。

    8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)197

    动机:两个类相互之间都需要对方的数据,但是相互之间只有一条单向的连接
    这个重构需要添加测试,因为“反向指针”很容易造成混乱。
    具体方法:

    • 在被引用类添加字段,保存引用类的指针;
    • 判断由哪个类来控制关联关系;
      • 如果两者都是引用对象,且关联关系为“一对多”的关系,那么就由“拥有单一引用”的对象作为控制者;
      • 如果A对象是B对象的部件,则由B对象负责控制关系;
      • 如果两者都是引用对象,且关联关系为“多对多”的关系,那么随意确定一个对象作为控制者。
    • 在被控端建立辅助函数,命名清晰地描述其用途;
      • 如果修改函数在控制端,则由其负责更新反向指针;
      • 如果修改函数在被控制端,则在控制端建立一个修改反射指针的函数,由修改函数调用其修改反向指针。
      • 两者是一对多关系,有单一引用承担控制关联关系责任

    8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)200

    动机:两个类有双向关联,但是一个类不再需要另一个类的特性
    原因:

    • 双向关联可能造成僵尸对象,不能被清除释放内存。
    • 使两个类存在耦合关系,一个类的变化会导致另一类的变化。方法:去除双向关联
      困难:检查可行性

    8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)204

    动机:有一个字面常量(除了0和1之外)
    方法:创建常量赋值以该字面常量,给予命名。

    8.10 Encapsulate Field(封装字段)206

    动机:一个类有public字段
    将它声明为private,并提供相应的访问函数

    8.11 Encapsulate Collection(封装集合)208

    动机:类中使用集合,但是集合不能提供给用户直接操作,而是提供函数操作集合,降低用户与集合之间的耦合度
    方法:提供函数返回集合的只读副本,并提供增加和删除集合元素的函数
    具体方法:

    • Java2:封装Set
    • Java1.1:封装Vector
    • 封装数组

    8.12 Replace Record with Data Class(以数据类取代记录)217

    动机:面对旧程序中Record数据结构,新建类取代它
    方法:为该记录创建一个“哑”数据对象。

    Type Code(类型码)

    常见于过去的C语言编程中,因为没有枚举,所以采用类型码的方式标注。这个重构遇到的机会比较小

    8.13 Replace Type Code with Class(以类取代类型码)218

    动机:类中的数值类型码不影响类的行为
    方法:以一个新类替代类型码

    8.14 Replace Type Code with Subclasses(以子类取代类型码)223

    动机:有一个不可变的类型码且影响类的行为
    标志:switch或者if-then-else类的条件表达式,这个重构是Replace Conditional with Polymorphism的准备工具
    方法:以子类取代这个类型码

    8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码)227

    动机:有一个类型码且影响类的行为,但是无法通过继承消除(类型码可变化)
    方法:以状态对象取代。

    8.16 Replace Subclass with Fields(以字段取代子类)232

    动机:各个子类唯一区别只在“返回常量的数据”的函数上
    方法:修改这些函数使它们返回超类的某个(新增)字段,然后销毁子类。

    第9章 简化条件表达式 237

    条件逻辑非常复杂,也非常难以理解,通过重构将复杂的逻辑展现为简单的逻辑块。
    有些重构方法看起来非常简单,因为重构最重要的思想不是方法有多精妙,而是传达了一个小步快走的理念。就是一次只完成一个小重构,然后测试确保没有错误。然后,再进行下一个小重构和测试。从而整个大重构通过多个简单的小重构完成,避免大重构出错后需要全部回滚的问题。

    9.1 Decompose Conditional(分解条件表达式)238

    动机:if-then-else语句,不同分支做不同事动机成大型函数,本身就难以阅读,尤其在带有复杂条件的逻辑中。方法:

    • 将if语句提炼为函数
    • 将then和else段落提炼为函数
    • 对于存在嵌套的条件逻辑,先判断是否可以用Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)消除。不行再分解每个条件

    9.2 Consolidate Conditional Expression(合并条件表达式)240

    动机:有一系列条件判断都服务于共同的目标
    方法:将这些条件判断合并为同一个表达式,再将这个表达式提炼为独立函数
    原因:

    • 只是一次条件检查,只是存在多个并列条件需要检查而已
    • 为Extract Method(提炼函数)做准备,通过函数名告知“为什么这么做”

    9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段)243

    动机:在条件表达式的不同分支中存在相同的代码
    方法:将这些重复代码搬移到条件表达式之外,多行代码还可以再提炼为独立函数。
    例如:当try和catch执行相同代码,可以将代码移到final区段。

    9.4 Remove Control Flag(移除控制标记)245

    动机:在循环执行的程序段中,某个变量定义为判断条件中的控制标记(control flag),增加了代码理解的复杂度
    方法:

    • 以break或者continue代替;
    • 也可以通过函数调用和return语句来实现。

    9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)250

    卫语句:如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查被称为“卫语句”(guard clauses)
    动机:函数中的条件逻辑使人难以看清正确的执行路径。
    方法:使用卫语句表现所有的特殊情况

    9.6 Replace Conditional with Polymorphism(以多态取代条件表达式)255

    动机:存在条件表达式根据对象的类型不同选择不同的行为
    方法:将表达式分支放进不同子类,然后重写方法,将原始函数提炼为抽象函数。

    9.7 Introduce Null Object(引入Null对象)260

    动机:需要再三检查对象是否为null
    方法:将null值替代为null对象,如果原始类不允许修改可以使用Null接口来检查“对象是否为Null”。

    9.8 Introduce Assertion(引入断言)267

    动机:某段代码需要对程序状态显式地表明某种假设
    方法:以断言明确表现这种假设
    具体方法: 断言在 发布的时候统统 被跳过

    第10章 简化函数调用

    使接口变得更加简洁易用的重构方法。

    • 修改函数名称,使之容易理解;
    • 缩短参数列表;
    • 不同的功能分离到不同的函数中;
    • 隐藏函数,提升接口的质量。

    10.1 Rename Method(函数改名)273

    动机:函数的名称不能说明函数的用途
    方法:将旧函数代码搬移到新函数,旧函数跳转到新函数。

    10.2 Add Parameter(添加参数)275

    动机:被调用的函数需要从调用函数中得到更多的信息
    方法:为被调用的函数添加参数
    抉择:

    • 现有参数是否提供足够的信息?
    • 这个函数是否应该移动到拥有该信息的对象中?
    • 加入新参数是否合适?
    • 如果需要的参数过多,是否需要使用Introduce Parameter Object(引入参数对象)?

    10.3 Remove Parameter(移除参数)277

    动机:函数不需要某个参数(不需要了就放弃,保留也需要付出代价)
    方法:

    • 如果是独立的函数,直接将该参数移除
    • 如果是多态函数,不能移除,就增加一个新的没有这个参数的函数,使调用者的工作得到简化

    10.4 Separate Query from Modifier(将查询函数和修改函数分离)279

    动机:某个函数既修改对象状态,又返回对象状态值。(使调用者担心误操作修改了不应该修改的数据,增加调用者的操作负担)
    本质:函数功能简洁、明确,如果一个函数具备多个功能,就把它们分离成多个函数。
    方法:建立两个不同的函数,其中一个负责查询,另一个负责修改。
    原则:

    • 任何一个有返回值的函数都不应该有看得到的副作用。
    • 编码中主要考虑的不是代码的效率,而是代码的易读性,效率可以在未来上线的时候再根据实际需要调整。多线程:将修改和查询函数封装在一个同步函数中分开调用。

    10.5 Parameterize Method(令函数携带参数)283

    动机:几个函数,做了类似的工作,只是代码中的系数不同
    方法:建立单一函数,以参数作为系数

    10.6 Replace Parameter with Explicit Methods(以明确函数取代参数)285

    动机:函数依赖于参数值的不同而采取不同的行为
    方法:针对该参数的每个可能值,建立独立函数。
    对比:与Parameterize Method(令函数携带参数)相反,但是目的都是把复杂的逻辑判断消除 目的:提供清晰的入口。
    如果参数值对函数行为影响不大,不应该采用此方法。

    10.7 Preserve Whole Object(保持对象完整)288

    动机:从某个对象取若干个值,把他们作为参数传给函数
    方法:改为调用整个对象
    目的:避免过长参数列表
    缺陷:如果传递的是值,那么函数只依赖那些值;如果传递的是对象,函数则依赖对象,会导致耦合
    注意:有时候函数使用了很多来自某个对象的数据,那么应该考虑使用(Move Method)将这个函数移到关系密切的对象中

    10.8 Replace Parameter with Methods(以函数取代参数)292

    动机:对象调用某个函数,并将所得结果作为参数传递给另一个函数,而接受该参数的函数本身也能够调用前一个函数
    方法:让参数接受者去除该项参数,并直接调用前一个函数

    10.9 Introduce Parameter Object(引入参数对象)295

    动机:有些参数总是自然地同时出现
    方法:用一个对象把这些参数包装起来进行传递
    目的:

    • 缩短参数列表长度;
    • 函数具有一致性,降低理解和修改代码的难度

    10.10 Remove Setting Method(移除设值函数)300

    动机:类的某个字段应该对象创建的时候被设置,然后不再改变
    方法:去掉该字段的设置函数

    • 如果对参数的运算很简单,而且只有一个构造函数,就可以直接在构造函数中初始化。
    • 如果修改复杂,或者有多个函数试图改变这个字段,那么就需要提供一个独立函数,并给予独立函数一个清楚表达用途的名字
    • 如果是子类希望修改超类的字段
      • 那么最好是使用超类的构造器实现改变;
      • 或者通过拥有能够清楚表达用途的名字的函数来实现。
    • 如果修改集合字段,请使用Encapsulate Collection(208)实现。

    10.11 Hide Method(隐藏函数)303

    动机:有一个函数,从来没有被任何类调用
    方法:将该函数设为private
    补充:函数可见度不够,在编译的时候就可以发现;而函数过见度过高,则需要通过一些工具(Lint)来辅助检查。

    10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数)304

    动机:创建对象时不仅仅是做简单的构建动作方法:将构造函数替换为工厂模式范例:

    • 根据整数(实际是类型码)创建对象;
    • 根据字符串创建子类对象;
    • 以函数创建子类;

    10.13 Encapsulate Downcast(封装向下转型)308

    动机:某个函数返回的对象,需要由函数调用者执行向下转型(downcast)
    方法:将向下转型移到函数中

    10.14 Replace Error Code with Exception(以异常取代错误码)310

    动机:某个函数返回一个特定的代码,表示某个错误的情况
    方法:取消那个代码判断,改用抛出异常
    范例:

    • 非受控异常:使用守卫语句检查这个异常情况;
    • 受控异常:需要修改的调用者函数和被调用者函数,步骤太大,容易出错。可以先创建一个临时的中间函数,保留原函数,使所有的调用都改为新函数后,删除原函数,再修改新函数名称,即可。

    10.15 Replace Exception with Test(以测试取代异常)315

    动机:本该由调用者自行检查的条件,由被调用者抛出了一个可控异常。
    方法:修改调用者,使它在调用函数之前做检查。
    补充:异常就应该放在可能发生异常的地方使用。即可以预测的,可以通过检查避免的,那就是错误,不该发生;不能预测的,无法通过检查避免的,那就是异常。例如:账户余额小于取钱数目,申请取钱这个就是错误;账户余额大于取钱数目,取不出钱来就是异常。

    第11章 处理概括关系 319

    概括关系(generalization,即继承关系、泛化关系)

    11.1 Pull Up Field(字段上移)320

    动机:两个子类拥有相同的字段
    方法:

    • 将该字段移动到超类,去除重复数据声明;
    • 将使用该字段的行为搬移到超类,去除关于这个字段的重复行为。
    • 考虑对超类的该字段使用Self Encapsulate Field(171)

    11.2 Pull Up Method(函数上移)322

    动机:有些函数,在各个子类产生相同的结果。
    方法:

    • 将该函数移动到超类
    • 如果被提升的函数引用了子类中的函数
      • 如果可以将引用函数提升,就一起提升
      • 如果不可以将引用函数提升,可以在超类里面那个抽象函数

    11.3 Pull Up Constructor Body(构造函数本体上移)325

    动机:你在各个子类拥有一些构造函数,它们的本地几乎完全一致
    方法:在超类新建一个构造函数,并在子类构造函数中调用它。
    具体方法:

    • 将共同代码放在子类构造函数起始处,然后再复制到超类构造函数中。
    • 将子类构造函数中共同代码删除,改用调用新建的超类构造函数。

    11.4 Push Down Method(函数下移)328

    动机:超类中的某个函数只与部分而非全部子类有关
    方法:将这个函数移到相关的子类去。

    11.5 Push Down Field(字段下移)329

    动机:超类中的某个字段只被部分而非全部子类使用
    方法:将这个字段移到需要它的那些子类去。

    11.6 Extract Subclass(提炼子类)330

    动机:类中的某些特性只被部分实例用到。
    方法:新建一个子类,将上面所说的那一部分特性移到子类中。
    具体情况:

    • 并不是出现类型码就表示需要用到子类,可以在委托和继承之间做选择。
    • 为子类新建构造函数,
      • 子类构造函数与超类构造函数拥有相同的参数列表,并且直接调用超类构造函数
      • 如果需要隐藏子类,可使用Replace Constructor with Factory Method(以工厂函数取代构造函数)
    • 找出超类调用点
      • 如果超类构造函数与子类不同,通过rename method方法可以解决。
      • 如果不需要超类实例,可以将超类声明为抽象类。
    • 逐一使用函数下移和字段下移将源类的特性移动到子类。

    11.7 Extract Superclass(提炼超类)336

    动机:两个类有相似特性。
    方法:为两个类建立一个超类,将相同特性移至超类。
    补充:Extract Class,Extract Subclass,Extract Superclass对比学习。

    11.8 Extract Interface(提炼接口)341

    动机:多个用户只使用类接口中的同一子集,或者两个类的接口有部分相同。
    方法:将相同子集提炼到独立的接口中。
    区别:提炼超类是提炼共同代码,提炼接口时提炼共同接口。
    具体动机:如果某个类在不同环境下扮演截然不同的角色,使用接口就是个好主意。接口还能帮助类隐藏一些对外的函数接口。

    11.9 Collapse Hierarchy(折叠继承体系)344

    动机:超类和子类之间区别不大。
    方法:将它们合为一体。

    11.10 Form TemPlate Method(塑造模板函数)344

    动机:你有一些子类,其中相应的函数以相同顺序执行类似的操作,但各个操作的细节有所不同。
    方法:将这些小操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也变得相同了。然后将原函数上移至超类,运用多态来避免重复代码。这样的原函数就是Template Method。
    原因:虽然使用了继承,但是函数重复应尽量避免。

    11.11 Replace inherited with Delegation(以委托取代继承)352

    动机:某个子类只使用超类接口中一部分,或是根本不需要继承而来的数据方法:在子类中新建一个字段用以保存超类,调整子类函数,令它委托超类,然后去掉两者之间的继承关系。

    11.12 Replace Delegation with Inherited(以继承取代委托)352

    动机:在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数,方法:让委托类继承受托类。注意:

    • 如果并没有使用受托类的所有函数,那么就不要使用这个方法。因为子类应该总是遵循超类的接口,如果委托过多可以通过Remove Middle Man(160)方法让客户端调用受托函数,或者Extract Superclass(336)让两个类的接口提炼到超类中;还可以使用Extract Interface(341)方法。
    • 如果受托对象被不止一个其他对象共享,而且受托对象是可变的时候,那么这种情况下,不能将委托关系替换为继承关系,因为这样就无法共享数据了。数据共享是委托关系的一种重要功能。
  • 《重构》的读书笔记–方法列表的更多相关文章

    1. 《HTML重构》读书笔记&思维导图

      最近读了<HTML重构>这本书,以下做出自己的总结归纳,大家可以一起学习交流. 什么是重构?重构是在不改变程序行为的基础上进行小的改动是代码基本逐渐完善的过程,通常需要一些自动化工具的帮助 ...

    2. 重构(Refactoring)技巧读书笔记(General Refactoring Tips)

      重构(Refactoring)技巧读书笔记 之一 General Refactoring Tips, Part 1 本文简要整理重构方法的读书笔记及个人在做Code Review过程中,对程序代码常用 ...

    3. 《C#图解教程》读书笔记之三:方法

      本篇已收录至<C#图解教程>读书笔记目录贴,点击访问该目录可获取更多内容. 一.方法那些事儿 (1)方法的结构:方法头—指定方法的特征,方法体—可执行代码的语句序列: (2)方法的调用:参 ...

    4. ITEYE中的读书笔记:重构其实就是持续改进

      原文地址:http://hawkon.iteye.com/blog/2093338#bc2355877 前段时间同事参加ITEYE的试读有奖, 没想到得了个奖,拿到一本书.由于同事的推荐我也认真读了一 ...

    5. 强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods)

      强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods) 学习笔记: Reinforcement Learning: An Introduction, Richard S ...

    6. 强化学习读书笔记 - 13 - 策略梯度方法(Policy Gradient Methods)

      强化学习读书笔记 - 13 - 策略梯度方法(Policy Gradient Methods) 学习笔记: Reinforcement Learning: An Introduction, Richa ...

    7. 强化学习读书笔记 - 11 - off-policy的近似方法

      强化学习读书笔记 - 11 - off-policy的近似方法 学习笔记: Reinforcement Learning: An Introduction, Richard S. Sutton and ...

    8. 强化学习读书笔记 - 10 - on-policy控制的近似方法

      强化学习读书笔记 - 10 - on-policy控制的近似方法 学习笔记: Reinforcement Learning: An Introduction, Richard S. Sutton an ...

    9. 强化学习读书笔记 - 09 - on-policy预测的近似方法

      强化学习读书笔记 - 09 - on-policy预测的近似方法 参照 Reinforcement Learning: An Introduction, Richard S. Sutton and A ...

    随机推荐

    1. SqlServer中的系统数据库

      SqlServer中的系统数据库有五个,平时写代码不太关注,今天一时兴起研究了一下. 1. master 记录SQL Server系统的所有系统级信息,例如:登陆账户信息.链接服务器和系统配置设置.记 ...

    2. CTF比赛 十一月场 Look 复现

      拿到题吧,一般的我的操作就是,先看看审核元素有有没有什么东西,然后去御剑扫描,git泄露,备份文件泄露,不行就再去burp抓包看看头部,换方法(post换成get) 发现不明的头部,这种头部的话可能是 ...

    3. zabbix安装及简单使用备注

      1.安装mysql yum install -y mariadb mariadb-server systemctl start mariadb 2.安装apache yum -y install ht ...

    4. SQL注入攻击的常见方式及测试方法

      本文主要针对SQL注入的含义.以及如何进行SQL注入和如何预防SQL注入让小伙伴有个了解.适用的人群主要是测试人员,了解如何进行SQL注入,可以帮助我们测试登录.发布等模块的SQL攻击漏洞,至于如何预 ...

    5. 【工具篇】Sublime Text 2 安装汉化破解、插件包安装教程详解

      Sublime Text概述: Sublime Text是一个代码编辑器,也是HTML和散文先进的文本编辑器. 漂亮的用户界面和非凡的功能,例如:迷你地图,多选择,Python插件,代码段等等. 完全 ...

    6. linux-Navicat 连接数据库 报错10060 & Navicat连接报错1146

      1.mysql数据库设置远程连接权限 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION;[回 ...

    7. Python基础(文件操作)

      文件读取: #文件读取方式一 f=open("a.txt","r+",encoding="utf8") data=f.read() prin ...

    8. ViewPagerWithRecyclerDemo【RecyclerView+ViewPager实现类似TabLayout+ViewPager效果】

      版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 使用RecyclerView+ViewPager实现类似TabLayout+ViewPager效果. 效果图 使用步骤 一.项目组织 ...

    9. Spring基础学习,附例子代码讲解

      什么是Spring.IOC.AOP.DI?     Spring是一个基于IOC和AOP的结构J2EE系统的框架.     IOC(Inversion Of Control)控制反转(Spring的基 ...

    10. javaweb + websocket实现客户端

      最近需要完成一个javaweb项目,但是没有和数据库连接而是通过websocket通讯实现和服务器端数据交互.我搜了好多,网上大部分都是通过页面websocket连接本地服务器或连接异地服务器,但是这 ...