某个类做了应该由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. 博弈---巴什博奕(Bash Game)(博弈入门)

    巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规 定每次至少取一个,最多取m个.最后取光者得胜. 显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走 ...

  2. Java 更改日期格式

    import java.util.*; import java.text.*; public class TestDateFormat { public static void main(String ...

  3. 关于mysql无法添加中文数据的问题以及解决方案

    今天弄了一天的mysql数据库,就是被一个mysql数据库乱码的问题给缠住了.现在记录一下这个问题,虽然这个问题不是什么太大的事情,但还是记录一下. 问题是这样的: 1.先在mysql的安装文件当中, ...

  4. lintcode-425-电话号码的字母组合

    425-电话号码的字母组合 Given a digit string excluded 01, return all possible letter combinations that the num ...

  5. lintcode-420-报数

    420-报数 报数指的是,按照其中的整数的顺序进行报数,然后得到下一个数.如下所示: 1, 11, 21, 1211, 111221, ... 1 读作 "one 1" -> ...

  6. Java 成员初始化顺序

    package com.cwcec.test; class Fu { int num = 5; //构造代码块 { System.out.println("Fu constructor co ...

  7. 超强汇总!110 道 Python 面试笔试题

    https://mp.weixin.qq.com/s/hDQrimihoaHSbrtjLybZLA 今天给大家分享了110道面试题,其中大部分是巩固基本python知识点,希望刚刚入手python,对 ...

  8. brush

    简介 Brushing是一个通过点击或触摸来选择一个一维或二维区域的交互操作,比如可以通过点击鼠标并移动. brush经常被用来选择离散的元素比如散点图中的点或桌面上的文件等.它也可以被用来放大选中的 ...

  9. 【Python】Python基础教程系列目录

    Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. 在现在的工作及开发当中,Python的使用越来越广泛,为了方便大家的学习,Linux大学 特推出了 <Python基 ...

  10. ZOJ2686_Cycle Gameu

    题目的意思是给你一个多边形,每条边上有一个权值,你开始在第一个点.每次你必须经过一条有权值的边,并且把该边的权值减小到任意一个非负值,到达该边的另外一个点. 谁第一个无法操作就算输. 题意很简单,解法 ...