某个类做了应该由两个类做的事。建立一个新类,将相关字段和函数从旧类搬移到新类。
我们都知道,一个类应该是一个清楚的抽象,处理一些明确的责任,但是在实际工作中,类会不断成长扩展,随着责任不断增加,这个类会变得过分复杂。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示应该将它们分离出去。一个有用的测试就是问自己,如果搬移了某些字段和函数,会发生什么事?其他字段和函数是否因此变得无意义?
另一个往往在开发后期出现的信号是类的子类化方式。如果你发现子类化只影响类的部分特性,或如果你发现某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味着你需要分解原来的类。
1.决定如何分解类所负的责任。
2.建立一个新类,用以表现从旧类中分离出来的责任。
如果旧类剩下的责任与旧类名不符,为旧类更名
3.建立“从旧类访问新类”的链接关系
有可能需要一个双向链接。但是在真正需要它之前,不要建立“从新类通往旧类”的链接
4.对于想搬移的没一个字段,运用Move Field 搬移之。
5.每次搬移后,编译、测试。
6. 使用Move Method将必要函数搬移到新类。先搬移较低层函数(“被其他函数调用”多于“调用其他函数”者),再搬移较高层函数。
7.每次搬移之后,编译、测试
8. 检查,精简每个类的接口
如果建立起双向连接,检查是否可以将它改为单向连接。
9决定是否公开新类。如果的确需要公开它,就要决定让它成为引用对象还是不可变的值对象。
我们从一个简单的person类开始
public class Person { private String name; private String officeAreaCode; private String officeNumber; public String getName() { return name; } public String getTelephoneNumber() { return ( "(" + officeAreaCode + ")" + officeNumber ); } public String getOfficeAreaCode() { return officeAreaCode; } public void setOfficeAreaCode( String arg ) { officeAreaCode = arg; } public String getOfficeNumber() { return officeNumber; } public void setOfficeNumber( String arg ) { officeNumber = arg; } }在这个例子中,我们可以将与电话号码相关的行为分离到一个独立类中。首先要定义一个TelephoneNumber类来表示“电话号码”这个概念
public class TelephoneNumber { }然后,建立从Person到TelephoneNumber的链接:
public class Person { private TelephoneNumber officeTelephone = new TelephoneNumber(); ... }
现在,我们运用Move Field移动一个字段:
public class TelephoneNumber { private String areaCode; String getAreaCode() { return areaCode; } void setAreaCode( String arg ) { areaCode = arg; } }
public class Person { public String getTelephoneNumber() { return ( "(" + getOfficeAreaCode() + ")" + officeNumber ); } public String getOfficeAreaCode() { return officeTelephone.getAreaCode(); } public void setOfficeAreaCode( String arg ) { officeTelephone.setAreaCode( arg ); }
然后我们可以移动其他字段,并运用Move Method将相关函数移动到TelephoneNumber类中:
public class TelephoneNumber { private String areaCode; private String number; public String getTelephnoeNumber() { return ( "(" + getAreaCode() + ")" + number ); } String getAreaCode() { return areaCode; } void setAreaCode( String arg ) { areaCode = arg; } String getNumber() { return number; } void setNumber( String arg ) { number = arg; } }
public class Person { private TelephoneNumber officeTelephone = new TelephoneNumber(); private String name; public String getName() { return name; } public String getTelephoneNumber() { return officeTelephone.getTelephnoeNumber(); } TelephoneNumber getOfficeTelephone() { return officeTelephone; } }下一步要做的决定是:要不要对用户公开这个新类? 我们可以将Person中与电话号码相关的函数委托至TelephoneNumber,从而完全隐藏这个新类;也可以直接将它对用户公开。我们还可以将它公开给部分用户。
如果选择公开新类,就需要考虑别名带来的危险。如果公开了TelephoneNumber,而有个用户修改了对象中的areaCode字段值,我们又怎么能知道呢?而且,做出修改的可能不是直接用户,而是用户的用户。
面对这个问题,我们又下列几种选择。
1.允许任何对象修改TelephoneNumber对象的任何部分。这就使得TelephoneNumber对象成为引用对象,于是我们应该考虑使用Change Value to Reference。这种情况下,Person应该是TelephoneNumber的访问点。
2.不允许任何人不通过Person对象就修改TelephoneNumber对象。为此,我们可以将TelephoneNumber设为不可修改的,或为它提供一个不可修改的接口。
3. 另一个办法是:先复制一个TelephoneNumber对象,然后将复制得到的新对象传递给用户。但这可能会造成一定程度的迷惑,因为人们会认为他们可以修改TelephoneNumber对象值。此外,如果同一个TelephoneNumber对象被传递给多个用户,也可能在用户之间造成别名问题。
Extract Class是改善并发程序的一种常用技术,因为它使你可以为提炼后的两个类分别加锁。如果你不需要同时锁定两个对象,就不必这样做。
这里也存在危险性。如果需要确保两个对象被同时锁定,就面临事务问题,需要使用其他类型的共享锁。这是一个复杂的领域,比起一般情况需要更繁重的机制。事务很有实用性,但是编写事务管理程序则超出了大多数程序员的职责范围。