重构——重新组织数据

1 Self Encapsulate Field(自封装字段)

直接访问一个字段,会导致字段之间的的耦合关系过于笨拙。为字段建立取值/设值函数,并且只以这些函数来访问字段。
  动机:可以通过覆写函数而改变获取数据的途径。
  做法:

  • 为带封装字段建立取值/设值函数。
  • 找出该字段的所有引用点,替换。
  • 声明该字段为private。
  • 复查,确保覆盖所欲引用点。
  • 编译,测试。

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

数据项需要与其他数据和行为一起使用才有意义。
  动机:当数据开始变得复杂,操作数据的操作开始变多,就会变成这样。
  做法:

  • 为替代数值新建一个类,在其中声明一个final字段,其类型和源类中的替换数据类型一样。然后在新类中加入这个字段的取值函数,再加上一个此字段的构造函数。
  • 编译。
  • 将源类中的待替换数值字段类型改为前面新建的类。
  • 修改源类中改字段的取值函数,令它调用新类的取值函数。
  • 如果源类的构造函数中用到这个带替换字段,我们修改构造函数,令他改用新函数的构造函数,令它改用新类的构造函数来对字段进行赋值动作。
  • 吸怪源类中待替换字段的设值函数,令他为新类创建一个实例。
  • 编译,测试。
  • 现在,可能要对新类使用Change Value to Reference。

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

从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象变成引用对象。
  Motivation:在引用对象和值对象间做选择有时不容易。有时候,你会从一个简单的值对象,在其中保存少量不可修改的数据。而后,你可能会希望给这个对象加入一些可修改数据,并确定对任何一个对象的修改都能影响到所有引用此一对象的地方。这时候需要将对象变成一个引用对象。
  做法:

  • 使用Replace Constructor with Factory Method。
  • 编译测试。
  • 决定由什么对象负责提供访问新对象的途径。
  • 决定这些引用对象应该预先创建好还是动态创建。
  • 修改工厂函数,令他返回引用对象。
  • 编译,测试。

4 Change Reference to Value

有一个引用对象,很小且不变,而且不易管理。将它变成一个值对象。
  Motivation:如果引用对象开始变得难以使用,也许就应该将它改为值对象。引用对象将会早成很复杂的关联。特别是在分布式系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
  做法:

  • 检查重构目标是否位不可变对象,或是否可修改位不可变对象。
  • 建立equals()和hashCode()。
  • 编译,测试。
  • 考虑是否可以删除工厂函数,并将构造器声明位public。

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

数组的各个元素各自代表的意义不同。以对象替换数组,对于数组中的每个元素,以一个字段来表示。
  Motivation:数组应该是用来组织容纳一组相似的对象。如果数组里容纳的对象的意义不同,那么会给带来很臭的味道。

  • 新建一个类表示数组所拥有的信息,并在其中以一个public字段保存原先存储的数组。
  • 修改数组的所有用户,让它们改用新类的实例。
  • 编译,测试。
  • 逐一位数组元素添加取值/设值函数。根据元素的用途,为这些访问函数命名。修改客户端代码,让它们通过访问函数取用数组内的元素。每次修改后编译并测试。
  • 当所有对数据的直接访问都转而调用访问函数后,将新类中保存该数组的字段申明位private。
  • 编译。
  • 对于数组内的每一个元素,在新类中创建一个类型相当的字段。修改该元素的访问函数,另它改用上述的新建字段。
  • 每修改一个元素,编译并测试。
  • 数组的所有元素都有了相应的字段之后,删除该数组。

6 Duplicate Observed Data(复制“被监视的数据”)

将该数据赋值到一个领域对象中。建立一个Observer模式,以同步对象和GUI对象内的重复数据。其实就是用Observer模式改造类。
  Motivation:一个分层良好的系统,应该将处理用户界面和处理业务逻辑分开。

7 Change Unidirectional Association to Bidirection

添加一个反向指针,并使修改函数能够同时更新两条链接。
  Motivation:被引用类需要得到其引用者以便进行某些处理。这时候就加入了反向指针。
  做法:

  • 在被引用类中增加一个字段,用以保存反向指针。
  • 决定由哪个类——引用端还是被引用端——控制关联关系。
  • 在被控制端建立一个辅助函数,其命名应该清楚指出它的有限用途。
  • 如果既有的修改函数在控制端,让它负责更新反向指针。
  • 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。

tips:下面是如何决定由哪一个类负责控制关联关系。

1.如果两者都是引用对象,关联是一对多的关系。那就由拥有单一引用的那一方承担控制者角色。
2.如果某个对象是组成另一个对象的部件,那么后者控制关联关系。
3.如果两者都是引用对象,关联是多对多,那么随便选一个,是哪个都无所谓。

8 Change Bidirectional Association to Unidirectional

去掉不必要的关联。
  Motivation:大量双向连接很容易造成“僵尸对象”;此外双向关联也迫使连个类之间有了依赖。
  做法:

  • 找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除指针。
  • 如果客户使用了取值函数,先运用Self Encapsulate Field 将待删除字段自我封装起来,然后使用Substitute Algorithm对付取值函数,令它不再使用该字段。
  • 如果客户并未使用取值函数,那就直接修改待删除字段的所有被引用点:改以其他途径获得字段所保存的对象。每次修改后,编译并测试。
  • 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑。然后移除该字段。

9 Replace Magic Number with Symbolic Constant

创建一个常亮,根据其意义为它命名,并将上述的字面数值替换位这个常亮。

10 Encapsulate Field

将类中存在的public字段声明位private,并提供相应的访问函数。

11 Encapsulate Collection

让返回集合的函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
  Motivation:如果直接返回集合的话,集合发生变化,我们的类是一无所知的,这是很可怕的事情;且暴露集合给用户也就是将类的过多实现细节暴露给用户。如果一个取值函数确实需要返回多个值,它应该避免用户直接操作对象内保存的集合,并隐藏对象内与用户无关的数据结构。另外,不应该位集合提设值函数,应该提供用以为集合添加/移除元素的函数。
  做法:

  • 加入为集合添加/移除元素的函数。
  • 将保存集合的字段初始化位一个空集合。
  • 编译。
  • 找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的添加/删除函数;也可以直接修改调用段。
  • 编译,测试。
  • 找出所有”通过取值函数获得集合并修改内容“的函数,修改这些函数,让它们改用添加/移除函数。每次修改后,编译并测试。
  • 修改玩上述所有“通过取值函数获得集合并修改集合内容”的函数后,修改取值函数自身,使它返回该集合的一个只读副本(或者是迭代器)。
  • 编译测试。
  • 找出取值函数的所有用户,从中找出应该存在与集合所属对象内的代码。Extract Method 和 Move Method到宿主对象去。

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

你需要面对传统变成环境中的记录结构,为该记录创建一个‘哑’数据对象。
  Motivation:总有可能遇到记录格式,那么就把记录转换位类把。
  做法:

  • 新建一个类,表示这个记录。
  • 对于记录中的每一项数据,在新建的类中建立对应一个private字段,并提供相应的getter/setter。

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

类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码。
  Motivation: 在以C位基础的变成语言中,类型码和枚举值还是很常见的,类型码可读性还是不错。但问题在于,类型码终归是别名,编译器看见的,进行类型检测的还是还是数值。使用类型码的实质其实就是使用一个数值,无法强制使用符号名。这会大大降低代码的可读性,滋生bug。
  做法:

  • 位类型码建立一个类
  • 修改源类实现,让它使用上述新建的类。
  • 编译,测试。
  • 对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新的类。
  • 注意修改源用户,让它们使用新接口。
  • 每修改一个用户,编译并测试。
  • 修改使用类型码的旧借口,并删除保存旧类型码的静态变量。
  • 编译,测试。

14 Replace Type Code with Subclasses(以子类代替类型码)

有一个不可变的类型码,会影响类的行为。以子类取代这个类型码。
  Motivation:如果面对的是不会影响宿主类的行为,可以使用Replace Type Code with Class。如果类型码会影响宿主类的行为,那么最好的办法就是借助多态去解决它。一般来说这种情况的标志就是像switch这种表达式。这种条件表达式可能有两种表现形式:switch或者是if-then-else结构。这种情况必须重构,用的是Replace Conditional with Polymorphism进行重构。但为了能够顺利进行那样的重构,首先应该将类型码转换为可拥有多态行为的继承体系。这样的一个继承体系应该以类型码宿主为基类,针对每种类型码各建立一个子类。
  做法:

  • 使用Self Encapsulate Field将类型码自我封装起来。
  • 为类型码的每一个数值建立一个相应的子类。每个子类中覆写类型码的取值函数,使其返回相应的类型码值。
  • 每建立一个新的子类,编译并测试。
  • 从超类中删掉保存类型码的字段。将类型码访问函数声明为抽象函数。
  • 编译,测试。

15 Replace Type Code with State/Strategy

有一个类型码,会影响类的行为,但无法继承手法消除。以状态对象取代类型码。
  Motivation:和Replace Type Code with Subclasses很相似,但是类型码在生命期间发生变化或者其他原因宿主不能被继承。可以用此方法重构。主要使用State模式或策略模式。

  • 使用Self Encapsulate Field将类型码自我封装起来。
  • 新建一个类,根据类型码的用途为他命名。
  • 位这个新类添加子类,每个子类对应一种类型码。
  • 在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码。
  • 编译。
  • 在源类中建立一个字段,用以保存新建的状态对象。
  • 调整源类中负责查询类型码的函数,将查询动作转发给状态对象。
  • 调整源类中为类型码设值的函数,将一个恰当的状态对象子类赋值给“保存状态对象字段”。
  • 编译,测试。

16 Replace Subclass with Field

你的各个子类的唯一差别只在“返回常量数据”的函数身上。修改这些函数,使它们返回 超类中的某个字段,然后销毁子类。
  Motivation:有一种变化行为叫做“常量函数”,它们会返回一个硬编码的值。若子类只有常量函数,是在没有足够存在的价值,应该去掉以避免额外的复杂性。
  做法:

  • 对所有子类使用Replace Constructor with Factory Method。
  • 如果有任何代码直接引用子类,令它改而引用超类。
  • 针对每个常量函数,在超类中声明一个final。
  • 为超类声明一个protected构造函数,用以初始化新增字段。
  • 新建或修改子类构造函数,使它调用超类的新增构造函数。
  • 编译,测试。
  • 在超类实现素有常量函数,令它们返回相应字段值,然后将该函数从子类中删除。
  • 每删除一个常量函数,编译并测试。
  • 子类中所有常量函数都被删除后,使用Inline Method将子类构造函数内联到超类的工厂函数中。
  • 编译,测试。
  • 将子类删除掉。
  • 编译,测试。
  • 重复内联构造函数,删除子类,知道所有子类删除完。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 《重构》读书笔记 总览 第一部分 第一章从实例程序出发,展示设计的缺陷,对其重构可以了解重构的过程和方法。 第二部...
    白桦叶阅读 2,371评论 2 5
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,139评论 9 118
  • 我近期的目标是提高儿子的学习热情和书写的认真,提升学习成绩,为小升初打下坚实的基础,顺利升入理想的初中。 一、继续...
    归韵阅读 124评论 0 2
  • 皮肤保湿工作是妹纸们每天必做的功课,如果皮肤干燥、脱皮,肯定是保湿工作没有做好,只有保湿做好了,皮肤才会水润白皙。...
    L姐的故事阅读 239评论 2 2