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

动机:一个类应该是一个清楚地抽象,处理一些明确的责任。但是在实际工作中,类会不断成长扩展。你会在这儿加入一些功能,在哪加入一些数据。给某个类添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的类。于是,随着责任不断增加,这个类会变得过分复杂。很快,你的类就会变成一团乱麻。

这样的类往往含有大量函数和数据。这样的类往往太大而不易理解。此时你需要考虑哪些部分可以分离出去,并将它们分离到一个单独的类中。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此依赖,这就表示你应该将它们分离出去。一个有用的测试就是问自己,如果搬移了某些字段和函数,会发生什么事?其他字段和函数是否因此变得无意义。

另一个往往在开发后期出现的信号时类的子类化方式。如果你发现子类化只影响类的部分特性,或如果你发现某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味着你需要分解原来的类。

做法:1、决定如何分解类所负的责任。

2、建立一个新类,用以表现从旧类中分离出来的责任。如果旧类剩下的责任与旧类名称不符,为旧类更名。

3、建立“从旧类访问新类”的连接关系。有可能需要一个双向连接。但是在真正需要它之前,不需要建立“从新类通往旧类”的连接。

4、对于你想搬移的每个字段,运用 Move Field (搬移字段)搬移之。

5、每次搬移后,编译、测试。

6、使用Move Method (搬移函数)将必要函数搬移到新类。先搬移较低层函数搬移到新类。先搬移较低层函数(也就是“被其他函数调用“ 多于 ”调用其他函数“者),再搬移较高层函数。

7、每次搬移之后,编译、测试。

8、检查,精简每个类的接口。如果你建立起双向连接,检查是否可以将它改为单向连接。

9、决定是否公开信类。如果你的确需要公开它,就要决定让它成为引用对象还是不可变的值对象

范例(Examples)

让我们从一个简单的Person class开始:

class Person...

public String getName() {

return _name;

}

public String getTelephoneNumber() {

return ("(" + _officeAreaCode + ") " + _officeNumber);

}

String getOfficeAreaCode() {

return _officeAreaCode;

}

void setOfficeAreaCode(String arg) {

_officeAreaCode = arg;

}

String getOfficeNumber() {

return _officeNumber;

}

void setOfficeNumber(String arg) {

_officeNumber = arg;

}

private String _name;

private String _officeAreaCode;

private String _officeNumber;

在这个例子中,我可以将「与电话号码相关」的行为分离到一个独立class中。首 先我耍定义一个TelephoneNumber class来表示「电话号码」这个概念:

class TelephoneNumber {

}

易如反掌!然后,我要建立从Person到TelephoneNumber的连接:

class Person

private TelephoneNumber _officeTelephone = new TelephoneNumber();

现在,我运用Move Field 移动一个值域:

class TelephoneNumber {

String getAreaCode() {

return _areaCode;

}

void setAreaCode(String arg) {

_areaCode = arg;

}

private String _areaCode;

}

class Person...

public String getTelephoneNumber() {

return ("(" +getOfficeAreaCode() + ") " + _officeNumber);

}

String getOfficeAreaCode() {

return _officeTelephone.getAreaCode();

}

void setOfficeAreaCode(String arg) {

_officeTelephone.setAreaCode(arg);

}

然后我可以移动其他值域,并运用Move Method 将相关函数移动到TelephoneNumber class中:

class Person...

public String getName() {

return _name;

}

public String getTelephoneNumber(){

return _officeTelephone.getTelephoneNumber();

}

TelephoneNumber getOfficeTelephone() {

return _officeTelephone;

}

private String _name;

private TelephoneNumber _officeTelephone = new TelephoneNumber();

class TelephoneNumber...

public String getTelephoneNumber() {

return ("(" + _areaCode + ") " + _number);

}

String getAreaCode() {

return _areaCode;

}

void setAreaCode(String arg) {

_areaCode = arg;

}

String getNumber() {

return _number;

}

void setNumber(String arg) {

_number = arg;

}

private String _number;

private String _areaCode;

下一步要做的决定是:要不要对客户揭示这个新口class?我可以将Person中「与电 话号码相关」的函数委托(delegating)至TelephoneNumber,从而完全隐藏这个新class;也可以直接将它对用户曝光。我还可以将它暴露给部分用户(位于同一个package中的用户),而不暴露给其他用户。

如果我选择暴露新class,我就需要考虑别名(aliasing)带来的危险。如果我暴露了TelephoneNumber ,而有个用户修改了对象中的_areaCode值域值,我又怎么能知道呢?而且,做出修改的可能不是直接用户,而是用户的用户的用户。

面对这个问题,我有下列数种选择:

1. 允许任何对象修改TelephoneNumber 对象的任何部分。这就使得TelephoneNumber 对象成为引用对象(reference object),于是我应该考虑使用 Change Value to Reference。这种情况下,Person应该是TelephoneNumber的访问点。
2. 不许任何人「不通过Person对象就修改TelephoneNumber 对象」。为了达到目的,我可以将TelephoneNumber「设为不可修改的(immutable),或为它提供一个不可修改的接口(immutable interface)。
3. 另一个办法是:先复制一个TelephoneNumber 对象,然后将复制得到的新对象传递给用户。但这可能会造成一定程度的迷惑,因为人们会认为他们可以修改TelephoneNumber对象值。此外,如果同一个TelephoneNumber 对象 被传递给多个用户,也可能在用户之间造成别名(aliasing)问题。

Extract Class 是改善并发(concurrent)程序的一种常用技术,因为它使你可以为提炼后的两个classes分别加锁(locks)。如果你不需要同时锁定两个对象, 你就不必这样做。这方面的更多信息请看Lea[Lea], 3.3节。

这里也存在危险性。如果需要确保两个对象被同时锁定,你就面临事务(transaction)问题,需要使用其他类型的共享锁〔shared locks〕。正如Lea[Lea] 8.1节所讨论, 这是一个复杂领域,比起一般情况需要更繁重的机制。事务(transaction)很有实用性,但是编写事务管理程序(transaction manager)则超出了大多数程序员的职责范围。

重构改善既有代码设计--重构手法12:Extract Class (提炼类)的更多相关文章

  1. 重构改善既有代码设计--重构手法11:Move Field (搬移字段)

    你的程序中,某个字段被其所驻类之外的另一个类更多的用到.在目标类建立一个新字段,修改源字段的所有用户,令它们改用新字段.        动机:在类之间移动状态和行为,是重构过程中必不可少的措施.随着系 ...

  2. 重构改善既有代码设计--重构手法13:Inline Class (将类内联化)

    某个类没有做太多事情.将这个类的所有特性搬移到另一个类中,然后移除原类. 动机:Inline Class (将类内联化)正好于Extract Class (提炼类)相反.如果一个类不再承担足够责任.不 ...

  3. 重构改善既有代码设计--重构手法16:Introduce Foreign Method (引入外加函数)&& 重构手法17:Introduce Local Extension (引入本地扩展)

    重构手法16:Introduce Foreign Method (引入外加函数)你需要为提供服务的类增加一个函数,但你无法修改这个类.在客户类中建立一个函数,并以第一参数形式传入一个服务类实例. 动机 ...

  4. 重构改善既有代码设计--重构手法08:Replace Method with Method Object (以函数对象取代函数)

    你有一个大型函数,其中对局部变量的使用,使你无法釆用 Extract Method. 将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型 ...

  5. 重构改善既有代码设计--重构手法07:Remove Assignments to Parameters (移除对参数的赋值)

    代码对一个 参数赋值.以一个临时变量取代该参数的位置.     int Discount(int inputVal, int quantity, int yearTodate) { if (input ...

  6. 重构改善既有代码设计--重构手法05:Introduce Explaining Variable (引入解释性变量)

      发现:你有一个复杂的表达式. 解决:将该复杂的表达式(或其中的部分)的结果放进一个临时变量,并以此变量名称来解释表达式用途. //重构前 if((platform.toUpperCase().in ...

  7. 重构改善既有代码设计--重构手法04:Replace Temp with Query (以查询取代临时变量)

    所谓的以查询取代临时变量:就是当你的程序以一个临时变量保存某一个表达式的运算效果.将这个表达式提炼到一个独立函数中.将这个临时变量的所有引用点替换为对新函数的调用.此后,新函数就可以被其他函数调用. ...

  8. 重构改善既有代码设计--重构手法02:Inline Method (内联函数)& 03: Inline Temp(内联临时变量)

    Inline Method (内联函数) 一个函数调用的本体与名称同样清楚易懂.在函数调用点插入函数体,然后移除该函数. int GetRating() { return MoreThanfiveLa ...

  9. 重构改善既有代码设计--重构手法01:Extract Method (提炼函数)

    背景: 你有一段代码可以被组织在一起并独立出来.将这段代码放进一个独立函数,并让函数名称解释该函数的用途. void PrintOwing(double amount) { PrintBanner() ...

  10. 重构改善既有代码设计--重构手法19:Replace Data Value with Object (以对象取代数据值)

    你有一笔数据项(data item),需要额外的数据和行为. 将这笔数据项变成一个对象. class Order... private string customer; ==> class Or ...

随机推荐

  1. Android开发第二阶段(6)

    今天:对sdcard的操作有了进一步的了解和深入,为程序可以自主扫描并添加sdcard的MP3格式文件 明天:最后的修正.

  2. web.config详解(转载)

    该文为转载 原文地址:http://www.cnblogs.com/gaoweipeng/archive/2009/05/17/1458762.html 花了点时间整理了一下ASP.NET Web.c ...

  3. App接口如何保证安全

    微信开发或者高德地图,百度地图什么的api要使用,使用之前都需要注册一个账号,然后系统会给你一个key,然后调用api的时候把key传给服务器. 平常公司内部开发项目时,直接用mvc为app客户端提供 ...

  4. lintcode-439-线段树的构造 II

    439-线段树的构造 II 线段树是一棵二叉树,他的每个节点包含了两个额外的属性start和end用于表示该节点所代表的区间.start和end都是整数,并按照如下的方式赋值: 根节点的 start ...

  5. 在.net项目中使用Consul

    1.创建.net core web程序并运行 2.在Consul中注册该服务 Consul支持两种服务注册的方式,一种是通过Consul的服务注册HTTP API,由服务自身在启动后调用API注册自己 ...

  6. WebService(二)

    使用eclipse开发webservice的服务器端以及客户端的简单实例 1.服务端 在eclipse中像建立一个web项目一样,new->Dynamic Web Project A.建一个需要 ...

  7. div跟随鼠标移动

    1.目标是实现div跟随鼠标而移动,分三种情况进行实现 a)首先获取div,进行绑定鼠标移动事件,给div开启定位功能 第一种实现方式,假如body的大小跟页面大小一样,则可以用这个方法. 1)获取鼠 ...

  8. 复利计算程序单元测试(C语言)

    对我们和复利计算程序,写单元测试. 有哪些场景? 期待的返回值 写测试程序. 运行测试. 我的复利计算程序是用C语言写的,不懂使用C语言的测试工具,所以用C语言的运行结果来反映测试结果. 测试模块(场 ...

  9. ci事务

    CI框架百问百答:CodeIgniter的事务用法?--第9问 时间 2013-06-06 10:57:45  CSDN博客 原文  http://blog.csdn.net/haor2756/art ...

  10. Struts创建流程

    1.启动服务,加载web.xml 并实例化StrutsPrepareAndExecuteFilter过滤器 2.在实例化StrutsPrepareAndExecuteFilter的时候会执行过滤器中的 ...