在对象的设计过程中,“决定把责任放在哪儿”是件非常重要的事情,如果设计不合理,会造成类因为承担过多责任而变得臃肿不堪,或者造成一个类“不负责任”而变得“贫血”。遇到这类情况,我们需要采用相应的手段对这种类进行恰当的重构,使其能够以优雅的姿态站立在程序之中。接下来介绍几种常用重构手段供大家参考。
1、搬移函数(Move Method)
如果一个类有太多行为,或者如果一个类与另一个类有太多合作而形成高度耦合,此时我会搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
example:
使用一个表示“账户”的Account类来说明这项重构:
class Account {
private AccountType _type;
private int _daysOverdrawn;
double overdraftCharge() {
if(_type.isPremium()) {
double result = 10;
if(_dayOverdrawn > 7) {
result += (_daysOverdrawn -7 ) * 0.85;
return result;
} else {
reutrn _daysOverdrawn * 1.75;
}
}
}
double bankCharge() {
double result = 4.5;
if(_daysOverdrawn > 0) {
result += overdraftCharge();
return result;
}
}
}
假设有几种新账户,每一种都有自己的“透支金额计费规则”。所以将overdraftCharge()搬移到AccountType类中去更合适。
class AccountType {
double overdraftCharge(int daysOverdrawn) {
if(isPremium) {
double result = 10;
if(daysOverdrawn > 7) {
result += (daysOverdrawn - 7) * 0.85;
return result ;
} else {
return daysOverdrawn * 1.75;
}
}
}
}
Account类调整后的样子:
class Account {
private AccountType _type;
private int _daysOverdrawn;
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
double bankCharge() {
double result = 4.5;
if(_daysOverdrawn > 0) {
result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
}
}
2、搬移字段(Move Field)
对于某个类的字段,如果存在其他类的函数比其自身类中的函数使用它的频率还高,那么就要考虑这个字段是不是应该做迁移了。
3、提炼类(Extract Class)
一个类应该是一个清楚的抽象,处理一些明确的责任。但是在实际工作中,类会不断成长扩展。你会在这儿加入一些功能,在那儿加入一些数据。给某个类添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的类。于是随着责任的不断增加,这个类会变的过分复杂。很快,这个类就会变成一团乱麻。
这样的类往往含有大量的函数(method)和数据(data)。这样的类往往太大而不易理解和维护。此时需要考虑哪些部分可以分离出去,并将它们分离到单独一个类中。如果某些数据和函数总是一起出现,某些数据经常同时发生变化甚至彼此依赖,这就表示你应该将它们分离出去。
example:
class Person {
private String _name;
private String _officeAreaCode;
private String _officeNumber;
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;
}
}
在这个例子中,可以将与电话号码相关的行为分离到一个独立的类中。
class TelePhoneNumber {
private String _areaCode;
private String _number;
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;
}
}
调整后的Person类:
class Person {
private TelePhoneNumber telePhoneNumber;
private String _name;
public String getTelePhoneNumber() {
return telePhoneNumber.getTelePhoneNumber();
}
public String getName() {
return _name;
}
void setName(String arg) {
this._name = arg;
}
}
4、将类内联话(Inline Class)
Inline Class与Extract Class正好是相反的执行过程。如果一个类不再承担足够的责任、不再有单独存在的理由,则可以将其融合进调用它最为频繁的类中,在此过程中要注意是否存在其他类中使用到了这个被融合掉的类,调整所有引用点,引用此融合后的类即可。
5、隐藏委托关系(Hide Delegate)
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层的委托关系。万一委托关系发生变化,客户也得相应的变化。你可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这么一来,即便将来发生委托关系上的变化,变化也将被限制在服务对象中,不会波及客户。
example:
class Person {
Department _departemnt;
public Department getDepartment() {
return _department;
}
public void setDepartment(Department arg) {
_department = arg;
}
}
class Department {
private String _chargeCode;
private person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
...
}
如果客户想知道某人的经理是谁,他必须先取得Department对象:
manager = john.getDepartment().getManager();
这样编码就是对客户揭露了Department的工作原理,于是客户知道:Department用以追踪“经理”这条信息。如果对客户隐藏Department,可以减少耦合。为了这一目的,在Person中建立一个简单的委托函数:
class Person{
Department _departemnt;
public Department getDepartment() {
return _department;
}
public void setDepartment(Department arg) {
_department = arg;
}
public Person getManager() {
return _department.getManager();
}
}
class Department {
private String _chargeCode;
private person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
}
这样一来,person的所有客户端就可以调用新函数:
manager = john.getManager();
同时移除Person中的访问函数getDepartment()就可以了。
以上几点便是针对对象进行重构一些常用手段,有时候可能利用其中某一种方法就可以完成理想的重构,但更多的时候你可能需要综合利用这些重构手法才能让对象变成你希望看到的样子,还是那句话,千万不要迷恋于具体的招式,而是要做到“手中无剑,心中有剑”,一切招式都是在无意识中使出来的。