《重构》学习笔记(05)-- 在对象之间搬移特性

在对象设计的过程中,“决定把责任放在哪儿”是最重要的事情之一。但无论使用对象技术多么娴熟,也无法保证在设计对象时一次做对。因此,需要进行重构,改变原有的设计。

Move Method(搬移函数)

简单的说就是将一个类中的函数搬移到另一个类中。


image

重构后


image

这种重构方式看似简单,其中的难点在于确定是否应该移动一个函数。通常,我们的做法如下:
  • 检查源类中被原函数使用的一切特性(包括字段和函数),考虑它们是否也该被搬移。
  • 检查源类中的子类和超类,看看是否有函数的其他声明。
  • 在目标类中声明这个函数。
  • 将源函数的代码复制到目标函数中。调整后者,使其能够正常运行。
  • 编译目标类。
  • 决定如何从源函数正确引用目标对象。
  • 决定是否删除源函数,或者将它当做一个委托函数保留下来。
  • 如果要移除源函数,将对所有源函数的所有调用,替换为对目标函数的调用。
  • 编译,测试。

Move Field(搬移字段)

Move Field与Move Method类似,将一个字段搬移到更适合的类中。


image

重构后


image

随着系统的发展,你会发现你需要新的类,并将现有的工作责任拖到新类中。通常我们的做法为:
  • 如果字段的访问级是public,使用Encapsulate Field(封装字段)将它封装起来。
  • 在目标类中建立与源字段相同的字段,并同时建立相应的设值、取值函数。
  • 决定如何在源对象中引用目标对象。
  • 删除源字段。
  • 将所有对源字段的引用替换为对某个目的函数的调用。

Extract Class(提炼类)

如果某个类做了应该由两个类做的事。那么建立一个新类,将相关的字段和函数从旧类搬移到新类。


image

重构后


image

一个类应该是一个清楚的抽象,处理一些明确的责任。有些类含有大量的函数和数据,这样的类往往太大而不易理解,此时需要考虑哪些部分可以分离出去,并将它们分离到一个单独的类中。如果子类化只影响类的部分特性,或某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味需要分解原来的类。做法:
  • 决定如何分解类所负的责任。
  • 建立一个新类,用以表现从旧类中分离出来的责任。
  • 建立从旧类访问新类的连接关系。
  • 运用Mvoe Field搬移每个需要搬移的字段。
  • 运用Move Method搬移必要的函数。先搬移较低层函数,再搬移高层函数。
  • 决定是否要公开新类。

Inline Class(将类内联化)

如果某个类没有做太多事情,可以将这个类中所有的特性搬移到另一个类中,然后移除原类。


image

重构后


image

做法:
  • 在目标类身上声明源类的public协议,并将其中所有函数委托至源类。
  • 修改所有源类引用点,改而引用目标类。
  • 编译、测试。
  • 运用Move Method和Move Field,将源类的特性全部搬移到目标类。

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

客户类通过一个委托类调用另一个对象,在服务类上建立客户所需的所有函数,用以隐藏委托关系。


image

重构为
[图片上传失败...(image-b9ec27-1560092577298)]
做法为:

  • 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数。
  • 调整客户,令它只调用服务对象提供的函数。
  • 每次调整后,编译并测试。
  • 如果将来不再有任何客户需要取用Delegate(受托类),便可移除服务对象中的相关访问函数。
  • 编译、测试。

Remove Middle Man(移除中间人)

移除中间人与Hide Delegate是逆操作。如果某个类做了过多的简单委托动作,那么让客户直接调用受委托类。


image

重构为


image

委托的代价是,委托对象的变化会引起服务器对象变化。当服务类完成成了委托类的中间人,那么请删除中间人吧!不断使用Hide Delegate和Move Middle Man调整程序结构。做法为:
  • 建立函数,用以获得受托对象。
  • 对于每个委托函数,在服务类中删除该函数,并让需要调用该函数的客户转为调用受托对象。
  • 处理每个委托函数后,编译、测试。

Introduce Foreign Method(引入外加函数)

如果你正在使用一个类,但是你需要一项新的服务,这个类却无法修改或无法供应。此时你应该在客户类中新建一个函数,并以第一参数形式传入一个服务类的实例。
例如,我们需要获取一个Date的下一天。

Date newStart = new Date(previousEnd.getYear(),
                     previousEnd.getMonth(),previousEnd.getDate()+1);

可以重构为:

Date newStart = nextDay(previousEnd);
private static Date nextDay(Date arg){
    return new Date(arg.getYear(), arg.getMonth(),arg.getDate()+1);
}

在进行本项重构时, 如果你以外加函数实现一项功能, 那就是一个明确信号: 这个函数原本应该在提供服务的类中实现.但是不要忘记: 外加函数终归是权宜之计. 如果不能把外加函数搬移到服务类中, 就把外加函数交给服务类的持有者, 请他帮你在服务类中实现这个函数.本项重构一般的做法为:

  • 在客户类中建立一个函数,用来提供你需要的功能。
    =》这个函数不应该调用客户类的任何特性。如果它需要一个值,把该值当作参数传给它。
  • 以服务类实例作为该函数的第一个参数。

Introduce Local Extension(引入本地扩展)

如果你需要为服务类提供一些额外函数,但你无法修改这个类。那么建立一个新的类,使它包含这些额外函数,让这个扩展品成为源类的子类或者包装类。


image

重构为


image

例如以Date类为例,如果我们需要扩展,我们可以使用子类:
class MfDateSub extends Date{
    public MfDateSub nextDay()...
    public int dayOfYear()...
}

或者使用扩展类

class MfDateWrap{
    private Date _original;
}

常用的做法:

  • 建立一个扩展类,将它作为原始类的子类或者包装类。
  • 在扩展类中加入转型构造函数。
  • 在扩展类中加入新特性。
  • 根据需要,将原始对象替换为扩展对象。
  • 将针对原始类定义的所有外加函数版移到扩展类中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • 《重构》读书笔记 总览 第一部分 第一章从实例程序出发,展示设计的缺陷,对其重构可以了解重构的过程和方法。 第二部...
    白桦叶阅读 2,397评论 2 5
  • chapter 1 重构,第一个案例 1.1 什么时候需要重构 需要为程序添加一个特性,但代码结构无法使自己方便的...
    VictorBXv阅读 2,033评论 0 1
  • 在对对象的设计过程中,“决定把责任放在哪儿”即使不是最重要的事,也是最重要的事情之一。 1 Move Method...
    hklbird阅读 529评论 0 1
  • 一. Move Method(搬移函数) 介绍 场景你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用...
    nimw阅读 341评论 0 0
  • 一,重构,第一个案例 这一章作者先用一个影片出租程序的案例,来演示重构的过程 每个Customer顾客可以租多部M...
    高稷阅读 10,818评论 1 19