Advertisement
Guest User

Untitled

a guest
Jun 20th, 2019
62
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 29.38 KB | None | 0 0
  1. ## 章节一 重构,第一个案例
  2.  
  3. 快速而随性(quick and dirty)地设计一个简单的程序并没有错,但是,如果这是复杂系统中具有代表性的一段。。。
  4.  
  5. > tip: 如果你发现自己需要为程序添加一个特性,而代码结构是你无法很方便地那么做,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性
  6.  
  7. 重构的第一步永远相同:为即将修改的代码建立一组可靠的测试环境。
  8.  
  9. > tip: 重构技术以微小的步伐修改程序。如果你犯下错误,很容易就可发现他。
  10.  
  11. > tip: 任何一个傻瓜都能写出计算机理解的代码。唯有写出人类理解的代码,才是优秀的程序员。
  12.  
  13. 在另一个对象属性的基础上使用`switch-case`语句,并不是什么好主意,如果不得不使用,也是在对象自己的数据上使用,而不是在别人的数据上使用。
  14.  
  15. > 就Java语言体系来说,GOF是java基础知识和j2EE知识之间一座隐形的桥。虽然它是隐性的,却是不可越过(缺少)的。整个设计模式贯穿一个原理:面对接口编程,而不是面对实现。
  16.  
  17.  
  18.  
  19. ## 章节二 重构原则
  20.  
  21. 两顶帽子比喻添加新功能和重构,软件开发过程中,你可能会发现自己经常变换帽子,无论如何你都应该清楚自己戴的是哪一顶帽子。
  22.  
  23. 『我不是个伟大的程序员:我只是个有着一些优秀习惯的好程序员而已。 —— Kent Beck』
  24.  
  25. 几乎任何情况下我都反对专门拨出时间进行重构。在我看来,重构本来就不是一件‘特别拨出时间做’的事情,重构应该随时随地进行。你不应该为重构而重构,你之所以重构,是因为你想做别的什么事,而重构可以帮助你把那些事做好。
  26.  
  27. ### 何时重构
  28.  
  29. - 三次法则
  30. - 添加功能时一并重构
  31. - 修补错误时一并重构
  32. - 复审代码时一并重构
  33.  
  34. > tip: 事不过三,三则重构。
  35.  
  36.  
  37.  
  38. 计算机科学是这样一门学科:它相信所有问题都可以通过多一个间接层来解决。
  39.  
  40. > tip: 不要过早发布接口。请修改你的代码拥有权政策,使重构更顺畅。
  41.  
  42.  
  43.  
  44. 关于性能,一件很有趣的事情是:如果你对大多数程序进行分析,你会发现它把大半时间都耗费在一小半代码上。如果你一视同仁地优化所有代码90%的优化工作都是白费劲儿,因为被你优化的代码有许多难得被执行起来。
  45.  
  46.  
  47.  
  48. 性能热点[hot spot]
  49.  
  50. ## 章节三 代码的坏味道
  51.  
  52. > `Duplicated Code`重复代码:同一个类中,两个互为兄弟的子类中,毫不相干的类中。
  53.  
  54. > `Long Method` 过长函数:你应该更积极进取地分解函数。
  55.  
  56. 我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西,写进一个独立的函数中,并以其用途(而非实现手法)命名。我们可以对一组或者短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地这么做。关键不在于函数的长度,而在于函数『做什么』和『如何做』之间的语义距离。
  57.  
  58.  
  59.  
  60. 如何确定该提炼哪一段代码呢?一个很好的技巧是:寻找注释。它们通常是指出『代码用途和实现手法间的语义距离』的信号。如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数去。
  61.  
  62. 条件和循环常常也是提炼的信号。你可以使用分解条件表达式处理条件式。至于循环,你应该将循环和其内的代码提炼到一个独立函数中。
  63.  
  64. > `Large Class`过大类
  65.  
  66. 如果单一类做太多事情,会出现大量的实例变量,可以使用`Extract Class`将数个变量一起提炼至新类中。提炼时应选择类内彼此相关的变量。
  67.  
  68. 一个类如果拥有太多代码,往往也适合使用`Extract Class`, `Extract SubClass`。这里有个有用的技巧:先确定客户端如何使用他们,然后运用`Extract Interface`为每一种使用方式提炼出一个接口。
  69.  
  70. > `Long Parameter List`过长参数
  71.  
  72. 抽取出一个参数对象。
  73.  
  74. > `Divergent Change`发散式变化:一个类受到多个变化影响
  75.  
  76. 一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。如果不能够做到这一点,你就嗅出两种紧密相关的刺鼻味道中的一种了。
  77.  
  78. 如果某个类经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了。把这个对象分成几个,这样每个对象就可以只因一种变化而需要修改,针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类中所有内容,都应该反应该外界变化。
  79.  
  80. > `Shotgun Surgery`散弹式修改:一个变化影响多个类
  81.  
  82. 如果遇到某种变化,必须在许多不同的类内做出小修改以响应,这个坏味道就是散弹式修改,和发散式变化正好相反。此时,应该使用`Move method`, `Move fild` 把所有需要修改的代码放进同一个类,
  83.  
  84. > `Feature Envy` 依恋情节
  85.  
  86. 函数对某个类的兴趣,高过对自己宿主类的兴趣,那就把函数移至另一个地点。有时候函数有一部分有依恋情节,那就先`Extract Method` 然后移动到另一个地点。
  87.  
  88. 有些设计模式(策略模式和访问者模式)破坏了这个规则。最根本的原则:将总是一起变化的东西放在一块儿。
  89.  
  90. > `Data Clumps`数据泥团
  91.  
  92. 类中的值域,多个函数中的相同参数,这些总是绑在一起出现的数据应该放进属于他们自己的对象中。
  93.  
  94. 一个好的评断方法是:删掉众多数据中的一笔。其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确的信号:你应该为他们产生一个新对象。
  95.  
  96. > `Primitive Obsession`基本类型偏执
  97.  
  98. 对象的一个极具价值的东西是:他们模糊(甚至打破)了横亘在基本数据和体积较大的类之间的界限。你可以轻松地编写出一些与语言内置类型无异的小型类。
  99.  
  100. > `Switch Statements`switch 惊悚现身
  101.  
  102. 面相对象程序的一个最明显特征就是:少用`switch-case`语句。大多数时候,一看到`switch`语句,就应该考虑以多态来替换他。问题是多态该出现在哪儿?switch语句常常根据类型码进行选择,你要的是“与该类型码相关的函数或类”,所以应该使用`ExtractMethod`将switch语句提炼到一个独立函数中,再以`MoveMethod`将他搬移到需要多态性的那个类里。
  103.  
  104. 如果只是单一函数有一些选择事例,且不想改动它们,那么多态就有点杀鸡用牛刀了。
  105.  
  106. > `Parallel Inheritance Hierarchies`平行继承体系
  107.  
  108. 这是散弹式修改的一个特殊情况,此时每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。
  109.  
  110. 应对策略是:让一个继承体系的实例引用另一个继承体系的实例。
  111.  
  112. > `Lazy Class`冗赘类:无用的类
  113.  
  114. > `Speculative Generality`夸夸其谈未来性:为未来功能预设的伏笔的类,会造成系统难以理解和维护
  115.  
  116. > `Temporary Field`令人迷惑的暂时字段
  117.  
  118. 某个实例变量仅为某个特定情形而设。这样的代码让人不易理解,因为`通常认为对象在所有时候需要它的所有变量` 。使用`Extract Class` 把这个变量和相关函数提炼到一个独立的类中。
  119.  
  120. > `Message Chains`过渡耦合的消息链
  121.  
  122. 此时应该使用`Hide Delegate` 。可以在消息链的不同位置进行这种重构。先观察消息链最终得到的对象是用来干什么的,看看能否以`Extract Method`把使用该对象的代码提炼到一个独立函数中,在运用`Move Method`把这个函数推入消息链。
  123.  
  124. > `Middle Man` 中间转手人
  125.  
  126. 如果你看到某个类有一半的函数都委托给其他类,这样就是过渡运用委托了。这时应该使用`Remove Middle Man`,直接和真正负责的对象打交道。如果这样的(使用委托的)函数比较少,使用`InlineMethod`把他们放入调用端。如果这个`MiddleMan`还有其他行为,可以使用`Replace Delegation with Inheritance`,把它们变成实责对象的子类,这样既可以扩展,又不必负担那么多的委托。
  127.  
  128. > `Inappropriate Intimacy` 狎昵关系
  129.  
  130. 有时候两个类过于亲密,话费太多时间去探究彼此的`Private`成分。继承往往造成过度亲密,子类对超类的了解总是超过后者的主观愿望。使用`Replace Inheritance with Delegation`
  131.  
  132. > `Alertnative Classes with Different Interfaces` 异曲同工的类
  133.  
  134. 如果函数签名不同,做着同一件事,请运用`Rename Method`根据用途重新命名。这往往不够,请反复使用`Move Method`甚至`Extract SuperClass`。
  135.  
  136. > `Incomplete Library Class` 不完美的类库
  137.  
  138. 如果只想修改库类的一两个函数,可以运用`Introduce Foreign Method`,如果想添加一大堆额外的行为,就得运用`Introduce Local Extension`。
  139.  
  140. > `Data Class`幼稚的数据类
  141.  
  142. `DataClass`是指拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。只是不会说话的数据容器。
  143.  
  144. 对于public 字段,使用`Encapsulate Field`将它们封装起来。
  145.  
  146. 对于容器类字段,使用`Encapsulate Collection` 把他们封装起来。
  147.  
  148. 对于不该被别的类修改的字段,使用`Remove Setting Method`。
  149.  
  150. 找出这些取值/设值函数被其他类运用的地点,尝试以`Move Method`把那些调用行为搬移到`Data Class`。
  151.  
  152. `Data Class` 必须承担一定的责任。
  153.  
  154. > Refused Bequest 被拒绝的馈赠
  155.  
  156. 子类应该继承超类的函数和数据,如果不想或不需要,这就意味着继承体系错误。需要为这个子类新建一个兄弟类,在把用不到的字段,方法都放入这个兄弟类中。
  157.  
  158. > Comments 过多的注释
  159.  
  160. 如果需要注释解释一块代码做了什么,试试`Extract Method`;
  161.  
  162. 如果函数已经提炼出来了,但还是需要注释来解释其行为,试试`Rename Method`;
  163.  
  164. 如果需要注释说明某些系统的需求规格,试试`Introduce Assertion`
  165.  
  166. > tips: 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
  167.  
  168. ## 章节四 构筑测试体系
  169.  
  170. 如果你想要重构,首要前提就是拥有一个可靠的测试环境。
  171.  
  172. 自我测试类:每个类都应该有一个测试函数,并以他来测试这个类。
  173.  
  174. > tip: 确保所有测试都完全自动化,让它们检查自己的测试结果。
  175.  
  176. > tip: 一整组测试就是一个强大的bug侦测器,能够大大缩减查找bug所需要的时间。
  177.  
  178. > tip: 每当接收到bug report ,请先撰写一个单元测试来揭发这只臭虫。
  179.  
  180. 观察类该做的所有事情,然后针对任何一项功能的任何一种可能失败情况,进行测试。
  181.  
  182. 测试应该是一种风险驱动行为,而不是编写大量测试,比如测试的public函数。
  183.  
  184. > tip: 编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。
  185.  
  186. > tip: 考虑可能出错的边界条件,把测试火力集中在那。
  187.  
  188. > tip: 当事情被大家认为应该会出错时,别忘了检查彼时是否有异常如预期般被抛出。
  189.  
  190. > tip: 不要因为测试无法捕捉臭虫,就不撰写测试代码,因为测试的确可以捕捉到大多数臭虫。
  191.  
  192. ## 章节五 重构列表
  193.  
  194. 真要表示货币金额,我会使用Quantity模式。
  195.  
  196. 重构的基本技巧:小步前进,频繁测试。
  197.  
  198. ## 章节六 重新组织函数
  199.  
  200. > `Extract Method`:提炼函数,你有一段代码可以被组织在一起并独立出来,将这段代码放入独立函数中,并让函数名称解释函数用途。
  201.  
  202. > `Inline Method`:将函数内联化,一个函数的本体与名称同样清楚易懂。在函数调用点插入函数本地,然后移除该函数。
  203.  
  204. > `Inline Temp` :将临时变量内联化,你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
  205.  
  206. > `Replace Temp with Query` :以查询取代临时变量。你的程序以一个临时变量保存某一表达式的运算结果。将这个表达式提炼到独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数调用。
  207.  
  208. > `Introduce Explaining Variable`:引入解释性变量,你有一个负责的表达式,将复杂表达式的结果放进一个临时变量,以此变量名称来解释表达式用途。在条件逻辑中,特别有价值。
  209.  
  210. > `Split Temporary Variable` : 分解临时变量,你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立,对应的临时变量。
  211.  
  212. “循环变量”和“结果收集变量”可以多次赋值,除了这两种情况,如果临时变量被赋值超过一次,就意味着在函数中承担了一个以上的责任。如果临时临时变量承担多个责任,就应该被替换成多个临时变量。
  213.  
  214. > `Remove Assignments to Parameters` : 移除对参数的赋值。代码对一个参数进行赋值,应该以一个临时变量取代该参数的位置。
  215.  
  216. > `Replace Method with Method Object` :以函数对象取代函数。你有一个大型函数,其中对局部变量的使用使你无法采用`Extract Method` 。将这个函数放入一个单独对象中,如此一来局部变量就变成了对象内的字段。然后你可以在同一个对象中,将这个大型函数分解为多个小函数。
  217.  
  218. > `Substitute Algorithm` :替换算法。你想要把某个算法替换为另一个更清晰的算法。将函数本体替换为另一个算法。
  219.  
  220. ## 章节七 在对象之间搬移特性
  221.  
  222. > `Move Method` :搬移函数。你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者或被后者调用。在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
  223.  
  224. > `Move Field` : 搬移字段。你的程序中,有个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改原字段的所有用户,令他们改用新字段。
  225.  
  226. > `Extract Class` :提炼类。某个类做了应该由两个类做的事。建立一个新类,将相关的字段和函数从旧类搬移到新类。
  227.  
  228. 一个类应该是一个清楚的抽象,处理一些明确的责任。
  229.  
  230. > `Inline Class` :将类内联化。某个类没有做太多事情,将这个类的所有特性搬移到另一个类中,然后移除原类。
  231.  
  232. > `Hide Delegate` : 隐藏委托关系。客户通过一个委托类来调用另一个对象。在服务类上建立客户所需的所有函数,用以隐藏委托关系。
  233.  
  234. > `Remove Middle Men` : 移除中间人。某个类做了过多的简单委托工作。让客户直接调用受委托类。
  235.  
  236. > `Introduce Foregin Method` :引入外加函数。你需要为提供服务的类增加一个函数,但你无法修改这个类。在客户类中建立一个函数,并以第一参数的形式传入一个服务类实例。
  237.  
  238. > `Introduce Local Extension` :引入本地扩展。你需要为服务类提供一下额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
  239.  
  240. ## 章节八 重新组织数据
  241.  
  242. > `Self Encapsulate Filed` :自封装字段。你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段设立`getter/setter`函数,并且只以这些函数来访问字段。
  243.  
  244. 我比较喜欢直接访问方式,直到这种方式给我带来麻烦为止。比如想访问超类中的字段,却又想在子类中对这个变量的访问改为一个计算后的值,此时是最该使用`Self Encapsulate Field`的时候。
  245.  
  246. > `Replace Data Value with Object` :以对象取代数据值。你有一个数据项,需要和其他数据和行为一起使用才有意义。将数据项变为对象。
  247.  
  248. 注意这样一条规则:值对象应该是不可修改内容的。
  249.  
  250. > `Change Value to Reference` :将值对象改为引用对象。你从一个类衍生出许多彼此相等的实例,希望将他们替换为同一个对象。将这个值对象变为引用对象。
  251.  
  252. > `Change Reference to Value` :将引用对象改为值对象。你有一个引用对象,很小且不可变,而且不易管理。将它变成一个值对象。
  253.  
  254. > `Replace Array with Object` :以对象取代数组。你有一个数组,其中的元素各自代表不同的东西。以对象替换数组,对于数组中的每一个元素,以一个字段来表示。
  255.  
  256. > `Dulplicate Observed Data` :复制“被监视数据”。你有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
  257.  
  258. > `Change Unidirectional Association to Bidirectional` :将单向关联改为双向关联。两个类都需要使用对方的特性,但其间只有一条单向连接。添加一个反向指针,并使修改函数能够同时更新两条连接。
  259.  
  260. > `Change Bidirectional Association to Unidirectional` :将双向关联改为单向关联。两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。去除不必要的关联。
  261.  
  262. > `Replace Magic Number with Symbolic Constant` :以字面常量取代魔法数。你有一个字面数值,含有特别含义。创造一个变量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
  263.  
  264. > `Encapsulate Field` :封装字段。你的类中存在一个public的字段。将它声明为private,并声明访问函数。
  265.  
  266. > `Encapsulate Collection` :封装集合。有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
  267.  
  268. ```java
  269. public Set getCourse() {
  270. return Collections.unmodifiableSet(mCourses);
  271. }
  272. ```
  273.  
  274. > `Replace Record with Data Class` :以数据类取代记录。你需要面对传统编程环境中的记录结构。为该记录创建一个“哑”数据对象。
  275.  
  276. > `Replace Typecode with Class` : 以类取代类型码。类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码。
  277.  
  278. > `Replace Typecode with Subclass` : 以子类取代类型码。你有一个不可变的类型码,它会影响类的行为。以子类取代这个类型码。
  279.  
  280. 如果类型码不会影响宿主类的行为,使用`Replace Typecode with Class`。如果影响宿主类的行为,最好的办法是借助多态来处理。一般来说这种情况的标志是`switch-case`,`if-then-else`。应该使用`Replace Conditional with Porlymorphism`进行重构。在这之前,首先应该将类型码替换为可拥有多态行为的继承体系,以类型码的宿主类为基类,并针对每一种类型码各建一个子类。
  281.  
  282. 有两种特例:(1)类型码值在对象创建之后发生了改变;(2)类型码宿主类有了子类。这时候应该使用`Replace Typecode with State/Strategy`。
  283.  
  284. > `Replace Typecode with State/Strategy` :以`State/Strategy`取代类型码。你有一个类型码,它会影响类的行为,但是你无法通过继承手法消除它。以状态对象取代类型码。
  285.  
  286. > `Replace Subclass with Fields` :以字段取代子类。你的各个子类的唯一差别,只在“返回常量数据”的函数身上。修改这些函数,使他们返回超类中的每个新增字段,然后销毁子类。
  287.  
  288. ## 章节九 简化条件表达式
  289.  
  290. > `Decompose Conditional` :分解条件表达式。你有一个复杂的条件语句,从if then else三个段落中分别提炼出独立函数。
  291.  
  292. 程序之中,复杂的条件逻辑是最常导致逻辑复杂度上升的地点之一。
  293.  
  294. > `Consolidate Conditional Expression` :合并条件表达式。你有一系列条件测试,都得到相同结果。将这些测试合并成一个条件表达式,并将这个表达式提炼成一个独立函数。
  295.  
  296. > `Consolidate Duplicate Conditional Fragments` :合并重复的条件片段。在条件表达式的每个分支上有着相同的一段代码。将这段重复代码搬移到条件表达式之外。
  297.  
  298. > `Remove Control Flag` :移除控制标记。在一系列布尔表达式中,某个变量带有“控制标记(control flag)”的作用。以break语句或return语句取代控制标记。
  299.  
  300. ```java
  301. set done to false
  302. while not done
  303. if (condition) {
  304. do something
  305. set done to true
  306. }
  307. next step of loop
  308. ```
  309.  
  310. > `Replace Nested Conditional with Guard Clauses` :以卫语句取代嵌套条件表达式。函数中的条件逻辑使人难以看清正常的执行路径。使用卫语句表现所有特殊情况。
  311.  
  312. 卫语句就是把复杂的条件表达式拆分成多个条件表达式,比如一个很复杂的表达式,嵌套了好几层的if - then - else 语句,转换为多个if语句,实现它的逻辑,这多条的if 语句就是卫语句。
  313.  
  314. > `Replace Conditional with Polymorphism` :以多态取代条件表达式。你手上有多个条件表达式,它根据对象类型的不同而选择不同的行为。将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
  315.  
  316. > `Introduce Null Object` :引入Null对象。你需要再三检查对象是否为null。将null值替换为null对象。
  317.  
  318. > `Introduce Assertion` :引入断言。某一段代码需要对程序状态做出某种假设。以断言明确表现这种假设。
  319.  
  320. 常常有这样一段代码:只有当某个条件为真时,该段代码才能正常运行。断言是一个条件表达式,应该总是为真。如果它失败,表示程序员犯了错误。
  321.  
  322. 实际上,程序的成品往往将断言统统删除。
  323.  
  324. 你可以新建一个Assert类,用于处理各种情况下的断言。
  325. ## 章节十 简化函数调用
  326.  
  327. > `Rename Method` :函数改名。函数的名称未能揭示函数的用途。修改函数名称。
  328.  
  329. 函数的名称应该准确表达它的用途。给函数命名有一个好办法:首先考虑给这个函数写上一句怎样的注释,然后想办法将注释变成函数名称。
  330.  
  331. 如果你看到一个函数名称不能很好地表达它的用途,应该马上加以修改。
  332.  
  333. 要想成为真正的编程高手,起名水平至关重要。(嚯 ~!)
  334.  
  335. > `Add Parameter` :添加参数。某个函数需要从调用端得到更多信息。为此函数添加一个对象参数,让该对象带进函数所需信息。
  336.  
  337. > `Remove Parameter` : 移除参数。函数本体不再需要某个参数。将该参数去除。
  338.  
  339. > `Separate Query from Modifier` :将查询函数和修改函数分离。某个函数即返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。
  340.  
  341. 任何有返回值的函数,都不应该有看得到的副作用。
  342.  
  343. > `Parameterize Method` :令函数携带参数。若干函数做了类似工作,但在函数本题中却包含了不同的值。建立单一函数,以参数表达那些不同的值。
  344.  
  345. > `Replace Parameter with Explicit Method` :以明确函数取代参数。你有一个函数,其中完全取决于参数值而采取不同行为。针对该参数值的每一个可能值,建立一个独立函数。
  346.  
  347. > `Preserve Whole Object `:保持对象完整。你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象。
  348.  
  349. > `Replace Parameter with Methods` :以函数取代参数。对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。
  350.  
  351. 你应该只在必要关头才添加参数,预先添加这个参数很可能并不是你所需要的。
  352.  
  353. > `Introduce Parameter Object` : 引入参数对象。某些参数总是很自然地同时出现。以一个对象取代这些参数。
  354.  
  355. 你常会看到特定的一组参数总是一起被传递。这就是数据泥团(Data Clumps)。可以运用一个对象包装这些数据,再以对象取代他们。
  356.  
  357. 本项重构的价值在于缩短参数列。
  358.  
  359. 尽量以“范围对象”取代用一对值表示一个范围的代码。
  360.  
  361. > `Remove Setting Method`:移除设值函数。类中的某个字段应该在对象创建时被设值,然后就不再改变。去掉该字段的所有设值函数,同时将该字段设为`final`.
  362.  
  363. > `Hide Method` :有一个函数,从来没有被其他任何类用到。将这个函数改为private。
  364.  
  365. 重构往往促使你修改函数的可见度。
  366.  
  367. > `Replace Constructor with Factory Method` :以工厂函数取代构造函数。你希望在创建对象时不仅仅是做简单的构建动作。将构造函数替换为工厂函数。
  368.  
  369. > `Encapsulate Downcast` :抽取向下转型。某个函数返回的对象,需要由函数调用者执行向下转型。将向下转型动作移到函数中。
  370.  
  371. > `Replace Error Code with Exception` :以异常取代错误码。某个函数返回一个特定的代码,用以表示某种错误情况。改用异常。
  372.  
  373. 代码的可理解性是我们虔诚追求的目标。(内心OS:不不不不~~~能跑就行)
  374.  
  375. > `Replace Exception with Test` :以测试取代异常。面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用函数之前先做检查。
  376.  
  377. “异常”只应该被用于异常的,罕见的行为,也就是那些产生意料之外的错误的行为,而不应该称为条件检查的替代品。
  378.  
  379. ## 章节十一 处理概括关系
  380.  
  381. 概括关系 - generalization - 即继承关系,主要是将函数上下移动于继承体系之中。
  382.  
  383. > `Pull Up Field` : 字段上移。两个子类拥有相同的字段。将该字段移至超类。
  384.  
  385. 本项重构从两方面减少重复:去除了重复的数据声明,去除重复的行为。
  386.  
  387. > `Pull Up Method` :函数上移。有些函数,在各个子类中产生完全相同的结果。将该函数移至超类。
  388.  
  389. > `Pull Up Constructor Body` : 构造函数本地上移。你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。在超类中新建一个构造函数,并在子类构造函数中调用它。
  390.  
  391. > `Push Down Method` : 函数下移。超类中的某个函数只与部分而非全部子类有关。将这个函数移到相关的子类去。
  392.  
  393. > `Push Down Field` : 字段下移。超类中的某个字段只被部分而非全部子类用到。将这个字段移到需要它的那些子类去。
  394.  
  395. > `Extract Subclass` : 提炼子类。类中的某些特性只被某些而非全部实例用到。新建一个子类,将上面所说的那一部分特性移到子类中。
  396.  
  397. > `Extract Superclass` : 提炼超类。两个类有相似特性。为这两个类建立一个超类,将相同特性移至超类。
  398.  
  399. > `Extract Interface` :提炼接口。若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。将相同的子类提炼到一个独立接口中。
  400.  
  401. > `Collapse Hierarchy` :折叠继承体系。超类和子类之间无太大区别。将它们合为一体。
  402.  
  403. 所谓重构继承体系,往往是将函数和字段在体系中上下移动。
  404.  
  405. > `Form Template Method` : 塑造模板函数。你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作细节上有所不同。将这些操作分别放入独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。
  406.  
  407. > `Replace Inheritance with Delegation` : 以委托取代继承。某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。在子类中新建一个字段保存超类;调整子类函数,令它改而委托超类;然后去掉两者之间的继承关系。
  408.  
  409. > `Replace Delegation with Inheritance` : 以继承取代委托。你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。让委托类继承受托类。
  410. ## 章节十二 大型重构
  411.  
  412. 应该根据需要安排工作,只在需要添加新功能或修补错误时才进行重构。不必一开始就完成整个系统的重构,重构程度只要能满足其他任务的需要就行了。
  413.  
  414. > `Tease Apart Inheritance`: 梳理并分解继承体系。某个继承体系同时承担两项责任。建立两个继承体系,并通过委托关系让其中一个可以调用另一个。
  415.  
  416. > `Convert Procedural Design to Object` :将过程化设计转化为对象设计。你手上有一些传统过程化风格的代码。将数据记录变为对象,将大块的行为变为小块,并将行为移入相关对象之中。
  417.  
  418. > `Seprate Domain from Presentation` :将领域和表述/显示分离。某些GUI类之中包含了领域逻辑。将领域逻辑分离出来,为它们建立独立的领域类。
  419.  
  420. MVC模式最核心的价值在于:它将用户界面代码(视图、展示层)和领域逻辑(模型)分离。
  421.  
  422. > `Extract Hierarchy` : 提炼继承体系。你有某个类做了太多工作(瑞士军刀般的类),其中一部分工作是以大量条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况。
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement