第八章 重新组织数据
8.1.Self Encapsulate Field(自封装字段)(171)
你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。
为这个字段建立取值/设值函数,并且只以这些函数来访问字段。
动机:子类可以通过覆写一个函数而改变获取数据的途径,懒加载。
做法:为待封装字段建立取值/设值函数。
找出改字段的所有引用点,将它们全部改为调用取值/设值函数。
将该字段声明为private。
复查,确保找出所有引用点。编译测试。
8.2.Replace Data Value with Object(以对象取代数据值)(175)
你有一个数据项,需要与其他数据的行为一起使用才有意义。
将数据项变成对象。
动机:数据项需要特殊行为。
做法:新建一个类,声明一个final字段,其类型和源类中的待替换数值类型一样。然后在新类中加入这个字段点取值函数,再加上一个接受此字段为参数的构造函数。编译。
将源类中的待替换数值字段的类型改为前面新建的类。
修改源类中该字段的取值函数,令它调用新类的取值函数。如果源类构造函数中用到了这个待替换字段修改构造函数,令它改用新类的构造函数来对字段进行赋值动作。
修改源类中待替换字段的设值函数,令它为新类创建一个实例。编译测试。(可能需要Change Value to Reference(179))
8.3.Change Value to Reference(将值对象改为引用对象)(179)
你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。
将这个值对象变成引用对象。
动机:对任何一个对象的修改都能影响到所有引用此一对象的地方。
做法(未理解):使用Replace Constructor with Factory Method(304)。编译测试。
决定由什么对象负责提供访问新对象的途径。
决定这些引用应用预先创建好,或是应该动态创建。
修改工厂函数,令它返回引用对象。编译测试。
8.4.Change Reference to Value(将引用对象改为值对象)
你有一个引用对象,很小且不可变,而且不易管理。
将它变成一个值对象。
动机:内存区域之间错综复杂的关联。
做法:检查重构目标是否为不可变对象,或是否可修改为不可变对象。
建立equals()和hashCode()。编译测试。
考虑是否可以删除工厂函数,并将构造函数声明为public()。
8.5.Replace Array with Object(以对象取代数组)(186)(废话。。。)
你有一个数组,其中有元素各自代表不同的东西。
以对象替换数组。对于数组中的每个元素,以一个字段来表示。
8.6.Duplicate Observed Data(复制“被监视数据”)(189)(KVO)
你有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。
将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
动机:(1)不同的用户界面来表现相同的业务逻辑;(2)与GUI隔离后,领域对象的维护和演化更容易。
做法:修改view类,使其成为model类的Observer。
针对GUI类中的model类使用Self Encapsulate Field(171)。编译测试。
在事件处理函数中调用设值函数,直接更新GUI组件。编译测试。
在model类中定义数据及其相关访问函数。
修改view类中的访问函数,将它们的操作对象改为领域对象(而非GUI组件)。
修改Observer的update(),获得的值修改GUI。编译测试。
8.7.Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
两个类都需要是使用对方的特性,但其间只有一条单向连接。
添加一个反向指针,并使修改函数能够同时更新两条连接。
动机:函数调用成本。
做法:在被引用类中增加一个字段,用以保存反向指针。
决定由哪个类——引用段还是被引用端——控制关联关系。
在被控制建立一个辅助函数。如果既有的修改在控制端,让那个它负责更新反向指针。
如果既有的修改函数在被控制,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。
(Order有Customer,Customer需要有一个set属性存Order)
8.8.Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)(200)
两个类之间由双向关联,但其中一个类如今不再需要另一个类的特性。
去除不必要的关联。
动机:双向关联会造成耦合和循环引用。
做法:如果客户使用了取值函数,先运用“Self Encapsulate Field(171)”将待删除字段自我封装起来,然后使用Subsititute Algorithm(139)对付取值函数。编译测试。
移除所有该字段的更新逻辑,然后移除该字段。编译测试。
8.9.Replace Magic Number with Symbolic Constant(以字面量取代魔法数)(204)
你有一个字面数值,带有特别含义。
创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
动机:魔法数:拥有特殊意义,却又不能明确表现出这种意义的数字。
做法:声明一个常量,令其值为原本的魔法数值。
找出这个魔法数的所有引用点。
检查是否可以使用这个新声明的常量来替换该魔法数。比那已测试。
8.10.Encapsulate Field(封装字段)
你的类中存在一个public字段。
将它声明为private,并提供相应的访问函数。
8.11.Encapsulate Collection(封装集合)(208)
有个函数返回一个集合。
让这个函数返回该集合的一个只读副本,并在这个类中提供add/remove集合元素的函数。
动机:避免暴露集合内所保存的其他内容。
8.12.Replace Record with Data Class(以数据类取代记录)(217)
你需要面对传统编程环境中的记录结构。
为该记录创建一个“哑”数据对象。
做法:新建一个类,表示这个记录。
对于记录中的每一项数据,在新建的类中建立对应的一个private字段,并提供响应的的setter/getter。
8.13.Replace Type Code with Class(以类取代类型码)(218)
类之中有一个数值类型码,但它并不影响类的行为。
以一个新的类替代该数值类型码。
动机:类型码还是要检测,替换为数值。
做法:为类型码建立一个类。修改源类实现,让它是哟给你上述新建的类。编译测试。
对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类。
逐一修改源类用户,让它们使用新接口。编译测试。
删除使用类型码的旧接口,并删除保存旧类型码的静态变量。
8.14.Replace Type Code with Subclasses(以子类取代类型码)(223)
你有一个不可变的类型码,它会影响类的行为。
以子类取代这个类型码。
动机:如果类型码会影响宿主类的行为,上一个不能解决,借助多态。
做法:使用Self Encapsulate Field(171)将类型码自我封装起来。
为类型码的每一个数值建立一个相应的子类。在每个子类中覆写类型码的getter,使其返回响应的类型码值。
从超类中删除保存类型码的字段。将类型码访问函数生迷宫为抽象函数。编译测试。
8.15.Replace Type Code with State/Strategy(以State/Strategy取代类型码)
你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它。
以状态对象取代类型码。
动机:上一个不能使用:类型码的值在对象的生命周期中发生变化或其他原因使得宿主类不能被继承。
做法:使用Self Encapsulate Field(171)将类型码自我封装起来。
新建一个类,这就是一个状态对象。为这个类添加子类,每个子类对应一种类型码。(上一个是Manager、Salesman是Employee的子类,这里是状态码,一个属性)
在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码。
在源类中建立一个字段,用以保存新建的状态对象。
调整原来中查询和设值的函数。编译测试。
8.16.Replace Subclass with Fields(以字段取代子类)
你的各个子类的唯一差别“返回常量数据”的函数身上。
修改这些函数,使它们返回超类中的某个(新增)字段,然后销毁子类。
动机:减少不必要的复杂。
做法:与上述三种方法相反。
第九章 简化条件表达式
9.1.Decompose Conditional(分解条件表达式)(238)
你有一个复杂的条件(if-then-else)语句。
从if、then、else三个段落中分别提炼出独立的函数。
动机:复杂条件逻辑降低代码可读性。
9.2.Consolidate Conditional Expression(合并条件表达式)(240)
你有一系列条件测试,都得到相同结果。
将这些测试合并为一个条件表达式,并将这个表达式提炼成为一个独立函数。
动机:检查条件不同,最终行为一样。
9.3.Consolidate Duplicate Conditional Fragments(合并重复的条件片段)(243)
在条件表达式的每个分支上有着相同的一段代码。
将这段重复代码搬移到条件表达式之外。
动机:一组条件表达式的所有分支都执行了相同的某段代码。
9.4.Remove Control Flag(移除控制标记)(245)
在一系列布尔表达式中,某个变量带有“控制标记”(control flag)的作用。
以break语句或return语句取代控制标记(BOOL)。
动机:多余的BOOL。
做法:找出对标记变量赋值的语句,代以恰当的break语句或continue语句。
9.5.Replace Nested Conditional with Guard Clauses(以卫语取代嵌套条件表达式)(250)
函数中的条件逻辑使人难以看清正常的执行路径。(卫语:条件为真时立即从函数中返回)
使用卫语句表现所有特殊情况。
动机:重复嵌套if-else但不相互影响。
9.6.Replace Conditional with Polymorphism(以多态取代条件表达式)(255)
你手上有个条件表达式,它根据对象类型的不同而选择不同而选择不同的行为。
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
9.7.Introduce Null Object(引入Null对象)(260)
你需要再三检查某对象是否为null。
将null值替换为null对象。
做法:为源类建立一个子类,使其行为就像是源类的null版本。在源类和null子类中都加上isNull()函数,前者返回false,后者返回true。
9.8.Introduce Assertion(引入断言)(267)
某一代码需要对程序状态做出某种假设。
以断言明确表现这种假设。
动机:只有当某个条件为真时,该段代码才能正确运行。
第10章 简化函数调用(以下方法大多常用且简单故不详细说明)
10.1.Rename Method(函数改名)(273)
函数的名称未能揭示函数的用途。
修改函数的名称。
10.2.Add Parameter(添加参数)(275)
某个函数需要从调用端得到更多信息。
为此函数添加一个对象参数,让该对象带进函数所需信息。
动机:必须修改一个函数,而修改后的函数需要一些过去没有的信息。
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)
你有一个函数,其中完全取决于参数值而采取不同行为。
针对该参数的每一个可能值,建立一个独立函数。
动机:与上一个相反。
10.7.Preserve Whole Object(保持对象完整)(288)
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
改为传递整个对象。
动机:来自同一对象的若干项数据作为参数,传递给某个函数。万一将来被调用的函数需要新的数据项,就必须查找并修改对此函数的所有调用。
10.8.Replace Parameter with Methods(以函数取代参数)(292)
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。
让参数接受者去除该项参数,并直接调用前一个函数。
动机:参数接受端是否可以通过与调用端相同的计算来取得参数值。
做法:将参数的计算过程提炼到一个独立函数中。
将函数本体内引用该参数的地方改为调用新建的函数。
全部替换完后,使用Remove Parameter(277)将该参数去掉。
10.9.Introduce Parameter Object(引入参数对象)(295)
某些参数总是很自然的同时出现。
以一个对象取代这些参数。
动机:特定的一组参数总是一起被传递。好几个函数都使用一组参数。
10.10.Remove Setting Method(移除设值函数)
类中的某个字段应该在对象创建时被设值,然后就不再改变。
去掉该字段的所有设值函数。
10.11.Hide Method(隐藏函数)(303)
有一个函数,从来没有被其他任何类用到。
将这个函数修改为private。
10.12.Replace Constructor with Factory Method(以工厂函数取代构造函数)(304)
你希望在创建对象时不仅仅是做简单的建构动作。
将构造函数替换为工厂函数。
10.13.Encapsulate Downcast(封装向下转型)(308)
某个函数返回的对象,需要由函数调用者执行向下转型。
将向下转型动作移到函数中。
10.14.Replace Error Code with Exception(以异常取代错误码)(310)
某个函数返回一个特定的代码,用以表示某种错误情况。
改用异常。
10.15.Replace Exception with Test(以测试取代异常)(315)(与上一个相反)
面对一个调用者可以预先检查的条件,你抛出了一个异常。
修改调用者,使它在调用函数之前先做检查。