核心内容源于《重构 改善既有代码的设计》(Refactoring Improving the Design of Existing Code——Martin Fowler著)。
初衷
Martin Fowler设计本章的目的是为了称述,把责任放置在合适的地方。
常常使用Move Method和Move Field简单地移动对象行为,就可以简单解决这些问题。如果这两个重构方法都需要用到,那么首先使用Move Field,再使用Move Method。
一个类往往会因为承担过多的责任而变得臃肿不堪。这种情况下,使用Extract Class将一部分责任分离出去,如果一个类变得责任较少,那么使用Inline Class将它融入另一个类中。
如果一个类使用了另一个类,运用Hide Delegate将这种关系隐藏起来通常是有帮助的。
有些时候隐藏委托会导致拥有者的接口经常变化,此时需要使用Remove Middle Man。
本章最后两项重构,Introduce Foreign Method 和 Introduce Local Extension比较特殊,只有当不能访问某个类的源码,却又想把责任移动进这个不可修改的类时,建议使用这种方法。
目录
1.7Introduce Foreign Method(引入外加函数)
1.8Introduce Local Extension(引入本地扩展)
1.1Move Method(搬移函数)
程序中,有个函数(aMethod)与其所驻类(Class 1)之外的另一个类(Class 2)进行更多交流:调用或者,或者被后者调用。
动机
“搬移函数”是重构理论的支柱。如果一个类有太多行为,如果一个类与另一个类有太多合作而形成高度耦合,请使用Move Method方法。
遵守单一职责原则。
如果出现下面这种函数,需要注意:
使用另一个对象的次数比使用自己所驻对象的次数还多。
之后判断,这个函数和哪儿个对象的交流比较多,之后决定其移动路径。
做法
现在假设有一个Account的类:
class Account{
public:
double overdraftCharge(){
if(_type.isPremium()){
double result = 10;
if(_daysOverdrawn > 7){
result += (_daysOverdrawn -7) * 0.85;
}
return result;
}
else {
return _daysOverdraw * 1.75;
}
}
double bankCharge(){
double result = 4.5;
if(_daysOverdrawn > 0){
result += overdraftCharge();
return result;
}
}
bool isPremium();
private:
Account _type;
int _dayOverdrawn;
};
假设有几种新账户,每一种都有自己的“透支金额计费规则”,现在要把Account 中的overdraftCharge()搬移到AccountType类中。
①观察overdraftCharge()使用的每一项特性,考虑是否和overdraftCharge()一起移动。
此例中,我们需要将_daysOverdrawn字段留在Account类中,因为这个值会随着不同账户而变化。
现在将overdraftCharge()移动代AccountType类中。
注意,因为在使用了Account的两个字段(成员变量),_type和_dayOverdrawn。要想办法建立联系,且把_daysOverdrawn字段留在Account类中。
将_daysOverdrawn按值传递给新函数。
class AccountType{
public:
double overdraftCharge(Account account){
if(account.isPremium()){
double result = 10;
if(account._daysOverdrawn > 7){
result += (account._daysOverdrawn -7) * 0.85;
}
return result;
}
else {
return account._daysOverdrawn * 1.75;
}
}
};
以上使用字段_dayOverdrawn显然有问题, 毕竟成员变量属于对象,此处调用的_dayOverdrawn是否是我们想要的,不得而知。
①建立一个函数去获得_dayOverdrawn的数值。
class AccountType{
public:
double overdraftCharge(Account account){
if(account.isPremium()){
double result = 10;
if(account.getDaysOverDrawn() > 7){
result += (account.getDaysOverDrawn() -7) * 0.85;
}
return result;
}
else {
return account.getDaysOverDrawn() * 1.75;
}
}
};
②直接将 _dayOverdrawn声明为静态成员变量。
现在将源函数的函数本体替换为一个简单的委托动作,测试完成后,直接在被调用的地方进行替换,完全替换结果如下所示。
class Account{
public:
double bankCharge(){
double result = 4.5;
if(_daysOverdrawn > 0){
Account account;
result += _type.overdraftCharge(account);
return result;
}
}
bool isPremium();
private:
AccountType _type;
int _daysOverdrawn;
};
1.2Move Field(搬移字段)
动机
在类之间移动状态和行为,是重构过程中必不可少的措施。
对于一个字段,在其所驻类之外的另一个类中有更多函数使用了它,就需要考虑搬移这个字段。
上述所谓的使用,可能是通过设置/取值函数间接进行的。
做法:
class Account{
private:
AccountType _type;
double _interestRate;
public:
double interestForAmount_days(double amount,int days){
return _interestRate * amount * days/365;
}
};
现在要把利率_interestRate搬运动AccountType类中。但是Account类中有数个函数已经使用了该字段,那么我们需要在AccountType中建立_interestRate字段以及相应的访问函数
class AccountType{
public:
void setInterestRate(double arg){
_interestRate = arg;
}
double getInteresrRate(){
return _interestRate;
}
private:
double _interestRate;
};
那么Account类就会变为:
class Account{
private:
AccountType _type;
double _interestRate;
public:
double interestForAmount_days(double amount,int days){
return _type.getInteresrRate() * amount * days/365;
}
};
1.3Extract Class(提炼类)
某个类做了过多的内容,不符合“单一职责原则”,责任冗余,需要进一步去分清楚责任。
建立一个新类,将相关的字段和函数从旧类中搬移到新类中。
动机
一个类应该是一个清楚的抽象,处理一些明确的责任。
当一个类责任冗余时,我们需要对其进行重构,重新划分责任。
做法
我们从Person这个类入手
class Person{
public:
string getName(){
return this->_name;
}
string getTelephoneNumber(){
return ( "(" + this->_officeAreaCode + ")"+ this->_officeNumber);
}
string getOfficeAreaCode(){
return _officeAreaCode;
}
void setofficeAreaCode(string arg){
this->_officeAreaCode = arg;
}
string getOfficeNumber(){
return _officeNumber;
}
void setOfficeNumber(string arg){
this->_officeNumber = arg;
}
private:
string _name;
string _officeAreaCode;
string _officeNumber;
}
这个类中,我可以将与电话号码有关的行为单独分离到一个独立的类中。首先我们定义一个TelephoneNumber类来表示“电话号码”这个概念。
class TelephoneNumber{
};
现在要建立从Person到TelephoneNumber的连接。
现在先移动一个字段_areaCode:
class TelephoneNumber{
public:
string getAreaCode(){
return _areaCode;
}
string setAreaCode(string arg){
this->_areaCode = arg;
}
private:
string _areaCode;
};
Person类中进行相应的改变:
class Person{
public:
string getTelephoneNumber(){
return ( "(" + getOfficeAreaCode() + ")"+ this->_officeNumber);
}
string getOfficeAreaCode(){
return _officeTelephone.getAreaCode();
}
void setofficeAreaCode(string arg){
_officeTelephone.setAreaCode(arg)
}
private:
TelephoneNumber _officeTelephone = new TelephoneNumber();
};
下面继续移动其他字段和函数
class TelephoneNumber{
public:
string getTelephoneNumber(){
return ("(" + _areaCode + ")"+ _number);
}
string getAreaCode(){
return _areaCode;
}
string setAreaCode(string arg){
this->_areaCode = arg;
}
string getNumber(){
return _number;
}
void setNumber(string arg){
this->_number = arg;
}
private:
string _areaCode;
string _number;
};
Person类如下:
class Person{
public:
string getName(){
return this->_name;
}
string getTelephoneNumber(){
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficephone(){
return _officeTelephone;
}
private:
string _name;
TelephoneNumber _officeTelephone = new TelephoneNumber();
};
函数:
TelephoneNumber getOfficephone(){
return _officeTelephone;
}
意义非凡!神来之笔!
使用Person类就可以调用TelephoneNumber类中的内容,比如:
Person martin = new Person();
martin.getOfficeTelephone().setAreaCode("781");
1.4Inline Class(将类内联化)
某个类没有做太多事情
将这个类的所有特性搬移到另一个类中,然后移除原类
动机
Inline Class正好与Extract Class相反。如果一个类不再承担足够责任,不再有单独存在的理由,缩减不必要的类。
做法
class Person{
public:
string getName(){
return this->_name;
}
string getTelephoneNumber(){
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficephone(){
return _officeTelephone;
}
private:
string _name;
TelephoneNumber _officeTelephone = new TelephoneNumber();
};
class TelephoneNumber{
public:
string getTelephoneNumber(){
return ("(" + _areaCode + ")"+ _number);
}
string getAreaCode(){
return _areaCode;
}
string setAreaCode(string arg){
this->_areaCode = arg;
}
string getNumber(){
return _number;
}
void setNumber(string arg){
this->_number = arg;
}
private:
string _areaCode;
string _number;
};
先将TelephoneNumber中所有“可见”(public)函数:
class Person{
public:
string getName(){
return this->_name;
}
string getTelephoneNumber(){
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficephone(){
return _officeTelephone;
}
string getAreaCode(){
return _officeTelephone.getAreaCode();
}
string setAreaCode(string arg){
_officeTelephone.setAreaCode(arg);
}
string getNumber(){
return _officeTelephone.getNumber();
}
void setNumber(string arg){
_officeTelephone.setNumber(arg);
}
private:
string _name;
TelephoneNumber _officeTelephone = new TelephoneNumber();
};
找出所有的TelephoneNumber的所有用户
Person martin = new Person();
martin.getOfficeTelephone().setAreaCode("781");
变成了:
Person martin = new Person();
martin.setAreaCode("781");
之后不断使用1.1和1.2的方法,消减多余的类。
1.5Hide Delegate(隐藏“委托关系”)
客户通过一个委托来调用另一个对象
在服务类上建立客户所需的所有函数,用以隐藏委托关系
动机
封装是对象的关键特征之一,封装意味着每个对象都应该尽可能减少了解系统的其他部分。如此,一旦发生变化,需要了解这一变化的对象就会比较少,这会使变化比较容易进行。
除去Private,我们会发现,还有更多值得封装的东西。
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系发生变化,客户也得相应变化。
万一委托关系发生变化,客户也要进行变化,不易于抵御变化。
做法
class Person{
Department _department;
public:
Department getDepartment(){
return _department;
}
void setDepartment(Department){
_department = arg;
}
};
class Department{
public:
Department(Person manager){
_manager = manager;
}
Person getManager(){
return _manager;
}
private:
string _chargeCode;
Person _manager;
};
如果客户希望知道某人的经理是谁,他必须先取得Department对象
manager = john.getDepartmen().getManager();
如此,编码对客户揭露了Department的工作原理,于是客户知道:Department用以追踪“经理”这条信息。
如果对客户隐藏Department,可以减少耦合。
为了这一个目的,我在Person中建立一个简单的委托函数
class Person{
Department _department;
public:
//....
Person getManager(){
return _department.getManager();
}
//....
};
现在,我得修改Person的所有客户,让它们改用新函数:
manager = john.getManager();
1.6Remove Middle Man(移除中间人)
与1.5的内容正好对应,某个类做了太多的委托,让客户直接调用委托类。
1.6与1.5的内容交相辉映,二者相互组合使用,不断调整,让代码更加的舒服和自然。
具体过程和1.5刚好相反,现在我们有Person和Department 两个部分
class Person{
Department _department;
public:
Person getManager(){
return _department.getManager();
}
};
class Department{
public:
Department(Person manager){
_manager = manager;
}
private:
Person _manager;
};
如果要移除中间人,我们在Person中建立一个函数获得委托对象:
class Person{
Department _department;
public:
Department getDepartment(){
return _department;
}
};
然后就逐一处理委托函数,转换为如下形式:
manager = john.getDepartmen().getManager();
1.7Introduce Foreign Method(引入外加函数)
你需要为提供服务的类增加一个函数,但你无法修改这个类
在客户类中建立一个函数,并以第一参数形式传入一个服务类实例
做法:
Date newStart = new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate() + 1);
将上述内容改为:
Date newStart = nextDay(previousEnd);
Date nextDay(Date arg){
return new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate() + 1);
}
1.8Introduce Local Extension(引入本地扩展)
你需要为服务类提供一些额外函数,但是你无法修改这个类。
建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或者包装类(多次出现1.7的情况时,需要将扩展函数放入一个类中)
如果只是增加一个函数,那么使用1.7,如果增加的函数超过两个,就很难办了。
你需要将这些外加函数放到一个合适的地方
两种标准对象技术——子类化(subclassing)和包装(wrapping)是显而易见的办法。
本地扩展是一个独立的类,也是被扩展类的子类型:它提供源类的一切特性,同时额外添加新特性。
子类化:
class Date{
public:
int getYear();
int getMonth();
int getDate();
};
class MfDateSub:public Date
{
public:
//构造函数需要委托给Date的构造函数
//使用Move Method将内容移动到类中
Date nextDay(){
return new Date(getYear(),getMonth(),getDate() + 1);
}
};
使用包装类:
class MfDateWrap{
public:
MfDateWrap(string dateString){
_original = new Date(dateString);
}
MfDateWrap(Date arg){
this->_original = arg;
}
//将扩展函数中需要使用到的Date的函数内容全部进行一次委托
int getYear(){
return _original.getYear();
}
int getMonth(){
return _original.getMonth();
}
int getDate(){
return _original.getDate();
}
//将使用Move Method将日期相关的内容全部进行移动
Date nextDay(){
return new Date(getYear(),getMonth(),getDate() + 1);
}
private:
Date _original;
};