重构 改善既有代码的设计——在对象之间搬移特性

在面向对象的设计过程中,“决定把责任放到哪”即使不是最重要的事情,也是最重要的事之一。

一.Move Method(搬移函数)

你的程序中,有个函数与其所驻类之外的另一个类有更多的交流:调用后者或被后者调用。

1.动机

A.如果一个类与另一个类有太多合作而高度耦合,就要搬移函数。通过这种方式,可使得类的职责更明确。

2.做法

A.检查源类中被源函数使用的一切特性(包括字段和参数),考虑他们是否也该被搬移;如果特性只在源函数中使用,直接将特性搬移;如果还有其他函数使用该特性,考虑将其他函数一起搬移;

B.检查源类的超类和子类,是否有对该函数的其他生命(如果有的话,你或许无法搬移,除非目标类也具有多态性)

C.在目标类中声明该函数(可以重新命名,能充分表明函数功用)

D.将源函数的代码复制到目标函数中,调整后者,使之能正常运行;

E.编译目标类

F.决定如何从源函数中引用目标对象

G.修改源函数,使之成为一个委托函数

H.编译、测试

I.决定是否删除源函数,或将它当作委托函数保留下来(如果源函数被多次调用的话可以考虑作为委托函数保留,此处较为灵活)

J.编译、测试


二.Move Field(搬移字段)

程序中,某个字段被其所驻类之外的另一个类更多的用到;在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段。

1.动机

每个类都有其功能和属性即职责所在,随着开发和重构的进行,修改类的功能和属性是无法避免的事情。如果一个属性更多的被其所驻类之外的其他类调用(设/取值),那么我们就需要考虑将该属性移到目标类中。

评:好比鹤立鸡群,格格不入,每个变量都有自己的归属,我们要做的就是带它们找到自己的人生意义——回家;


2.做法

A.如果字段的访问级别是public,使用Encapsulated Field将变量封住起来;

B.编译、测试;

C.在目标类中新建字段,并建立相关读写函数;

D.在源对象中引用目标对象;

E.删除源对象中的字段;

F.将所有对源字段的引用替换成对某个目标函数的调用;

G.编译、测试;


三.Extract Class(提炼类)

某个类做了应该由两个或多个类做的事情,建立一个新类,将相关函数和字段移到新类中。

1.动机

类是具有相同属性和功能的对象。也就是说类是具有相同属性和功能的对象的抽象。这也就意味着每个类的职责都是很明确的。如果一个类既做A又做B同时又做C,那么到了最后这个类到底做什么可能连开发者都不清楚了。面向对象就是面向抽象,就是分别面向A、B、C。我们要做的就是明确分工,接口化合作。


评:明确分工,各司其职,方便管理,提升效率;


2.做法

A.决定如何分解类负担的责任;

B.新建一个类,用以表现从旧类中分解出来的责任;

C.建立从旧类访问新类的连接关系;

D.搬移你想搬移的字段,用Move Field搬移;

E.每次搬移后,编译、测试;

F.搬移你想搬移的方法,用Move Method搬移(先搬较底层函数,再搬较高层函数);

G.每次搬移后,编译、测试;

H.检查,精简每个类的接口;

I.决定是否公开新类,如果你的确需要公开它,就要决定让它成为引用对象还是不可变的值对象;


四.Inline Class(内联类)

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


1.动机

类是功能的抽象,当一个类无法再承担一定的责任,那么类也就失去了存在的价值(可能是重构移去了这个类的责任),采用Inline Class将萎缩的类放到另外一个类中;


2.做法

A.在目标类中声明源类的public协议,并将其中所有函数委托至源类;

B.修改所有源类引用点,改而引用目标类(将源类修改为private,断绝包外引用可能;同时修改源类名称,发现所有隐藏的调用点);

C.编译、测试;

D.运用Move Method和Move Field将所有特性移到目标类中;

E.为源类举行简单的丧礼;


五.Hide Delegate(隐藏委托关系)

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


1.动机

  封装意味每个对象都应该尽可能少的了解其他对象,如此一来,一旦该对象发生变化,需要了解该变化的对象就会比较少,变化也相对容易。


2.做法

A.对于每一个委托关系中的函数,在服务器对象建立一个简单的委托函数;

B.调整客户,令它只访问服务器对象的函数;

C.每次调整后,编译并调试;

D.如果不再有用户访问委托函数,便可移除服务对象中的相关访问函数;

E.编译、测试;


评:这种手法实际的使用场景是什么呢?



六.Remove Middle Man(移除中间人)

某个类做了过多简单委托工作;让客户直接调用受托类;


1.动机:

在Hide Delegate一节中,讲了“封装受托对象的好处”,但是,这层封装是有代价的;代价就是每当客户端使用受托类的新特性时 ,都要在服务端添加一个简单委托函数,随着受托类的特征不断增加,这一过程会令你痛苦不已。服务类完全变成了一个“中间人”,此时就应该让客户直接调用受托类。

很难说什么程度的封装是合适的,不过有了Hide Delegate 和Remove Middle Man之后,可以在系统运行过程中不断调整;随着系统的变化,“合适的封装程度”的尺度也是变化的。重构的意义就在于“你永远不必说对不起——只要把出问题的地方修补好了就行了”。


2.做法

A.建立一个函数,用以获得受托对象;

B.删除服务端委托函数,并让需要该函数的客户转而调用受托函数;

C.处理每个函数后,编译、测试;


七.Introduce Foreign Method(引入外加函数)

你需要为提供服务的类增加一个函数,但你无法修改这个类;在客户类中新建一个函数,并以第一参数的形式传入一个服务类实例。


1.动机

这种事情经常发生,你在使用一个类,它真的很好,提供了你需要的服务。而后你又需要一个新的服务,这个类却无法供应。如果有源码的话,你可以直接修改源码,如果不能的话,你就需要在客户端自行编码,添加新函数;

如果客户只适用一次,那么额外编码倒是无妨,但是如果频繁调用的话,就需要抽象成一个函数,“重复代码时程序的万恶之源”。进行重构时,如果你以外加函数实现一项功能,那就是一个明确信号:“该功能应该在服务端实现的”。

如果为某一个类建了大量的外加函数,或者有许多类都需要此种重构,那就不应该采用本项重构,而要采用Introduce Local Extension。但是不要忘记,外加函数终归是权宜之计,可能的话还是要将这些代码放到理想的位置,如果由于代码所有权的原因使你无法搬移,就把外加类交给服务类的拥有者,请它帮你在服务类中实现这个函数。


2.做法

A.在客户类中新建外加函数,用来提供你需要的功能(这个函数不应该调用客户类的任何特性,如果需要的话,就作为参数传入进去)

B.以服务类的实例作为第一参数传入进去

C.把函数注释为“外加函数,应该服务类实现”(如此,一旦将来有机会把外加函数移入到服务类,便可以轻松找出这些函数)

 

评:当你没有资源的时候,只能和掌握资源的人交谈,企图获取资源。即借力东风;


八.Introduce Local Extension(引入本地扩展)

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


1.动机

类的作者往往无法预料使用者的需求,当已有的服务类无法满足我们的需求而且我们又无法修改源服务类时,只能通过扩展函数的形式曲线救国。如果需要编辑的函数不超过两个,那么使用Introduce Foreign Extension是个不错的选择,但是如果函数数量大于2个的话(不易于管理),那么使用Introduce Local Extension就会是一个不错的选择。两种标准对象技术——子类化(subclassing)和包装(wrapping)是显而易见的方法,这种情况下,我们把子类化和包装统称为本地扩展(Local Extension)

所谓本地扩展是一个独立的类,同时也是被扩展的子类型,它具有源类的一切特性,同时额外添加新特性。在任何使用源类的地方,都可以使用本地扩展取而代之。


2.做法

A.建立一个扩展类,将它作为原始类的子类或包装类;

B.在扩展类中加入转型构造函数(即接受原对象作为参数的构造函数,如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装化方案,那么转型构造函数应该将它得到的参数以实例变量的形式保存起来,用作接受委托的原对象;

C.在扩展类中加入新特性;

D.根据需要,将原对象换成扩展对象;

E.将针对原始类定义的所有外加函数搬移到扩展类中;


3.范例

A.使用子类化

2.包装类



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值