浅墨浓香

想要天亮进城,就得天黑赶路。

导航

1. 搬移函数(Move Method)

1.1 动机

(1)某函数与其所驻类之外的另一个类进行更多交流(调用后者或被后者调用),这时可以在后者中建立一个类似行为的新函数。将旧函数变成一个委托函数或将其完全移除。

(2)当类中存在这样的函数:使用另一个对象的次数比使用自己所驻对象的次数还多,这时可以考虑将函数搬移到另一个对象中。

1.2 做法

(1)检查源类中被源函数所使用的一切特性(包括字段和函数),考虑它们是否也该被搬移。如果某个特性只被你打算搬移的那个函数用到,就应该将它一并搬移。如果另有其他函数使用了这个特性,可以考虑将使用该特性的所有函数全都一并搬移。

(2)检查源类中的子类或父类,看看是否有该函数的其他声明。如果出现其他声明,可能就无法进行搬移,除非目标类也同样表现出多态性。

(3)在目标类中声明这个函数(可以为此函数选择一个新名称)

(4)将源函数的代码复制到目标函数中。调整后者,使用能在新的类中正常运行。如果目标函数使用了源类中的特性,可以将源对象当作参数,传递给新的目标函数

(5)决定如何从源函数正确引用目标对象。(如,可在源类中新建一个字段来保存目标对象)

(6)修改源函数,使之成为一个纯委托函数。

(7)决定是否删除源函数,或将它当作一个委托函数保留下来。如果要移除源函数,还得将源类中所有对源函数的调用,替换为对目标函数的调用。

1.3 范例

//搬移函数
//重构前
class Account
{
private:
    AccountType _type; //账户类型
    int _daysOverdrawn;
public:
    //每种账户都有自己的“透支金额计费规则”,该函数可
    //被搬移到AccoutType类中去。
    double overdraftCharge()
    {
        if(_type.isPremium())
        {
            double result = 10;
            if(_daysOverdrawn > 7)
                result += (_daysOverdrawn - 7) * 0.85;
            
            return result;
        }
        else
        {
            return _daysOverdrawn * 1.75;
        }            
    }
};

//重构中
//1. 观察overdraftCharge()使用的每一项特性,考虑是否将它们与
//该函数一起被搬移。本例中_daysOverdrawn不会随不同种类账户而变化
//可留在Account类中。
//2.i当需要使用源类的特性时,有4种选择:
//(1)将这个特性也移到目标类
//(2)建立或使用一个从目标类到源类的引用关系。
//(3)将源对象当作参数传给目标函数
//(4)如果所需特性是个变量,将它当作参数传给目标函数。

//重构后
class Account...
{
public:
    //该函数可以用委托方式来代替,也可以删除(但必须同时修改所有对该函数的引用点)
    double overdraftCharge()
    {
        return _type.overdraftCharge(_daysOverdrawn); //委托方式
    }
};

class AccoutType...
{
public:
    //使用到源函数中的特性daysOverdrawn,这里将其作为参数传递给目标函数。
    double overdraftCharge(int daysOverdrawn)
    {
        if(isPremium())
        {
            double result = 10;
            if(daysOverdrawn > 7)
                result += (daysOverdrawn - 7) * 0.85;
            
            return result;
        }
        else
        {
            return daysOverdrawn * 1.75;
        }         
    }
};

2. 搬移字段(Move Field)

2.1 动机

(1)某个字段被其所驻类之外的另一个类更多地使用到,可以在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。上述所谓“使用”可以是通过get/setter函数间接进行的。

(2)使用Extract Class时,也可能需要搬移字段,此时可以先搬移字段,然后再搬移函数。

2.2 做法

(1)如果字段的访问级是public,使用Encapsulate Field将它封装起来。

(2)在目标类中建立与源字段相同的字段,并同时建立相应的设值/取值函数。

(3)决定如何在源对象中引用目标对象。在源类中建一个字段来保存目标对象。

(4)删除源字段,将所有对源字段的引用替换为某个目标函数的调用。

2.3 范例

//搬移字段
//重构前
class Account...
{
private:
    AccountType _type;
    double _interestRate; //要搬移的字段
    
public:
    //引用了_interestRate字段的函数
    double interestForAmount_days(double amount, int days)
    {
        return _interestRate * amount * days / 365;
    }
};

//重构后
class Account...
{
private:
    AccountType _type;
    double _interestRate; //如果有多个函数引用了该字段可以用
                          //Self-Encapsulation封装起来
    //对_interestRate进行封装
    void setInterestRate(double arg)
    {
        _type.setInterestRate(arg);
    }
    
    double getInterestRate(arg)
    {
        return _type.getInterestRate();
    }
                          
public:  
    //引用了_interestRate字段的函数,使用getInterestRate替代
    double interestForAmount_days(double amount, int days)
    {
        return getInterestRate * amount * days / 365;
    }  
};

class AccoutType...
{
private:
    double _interestRate;
    
public:
    //新建设值/取值函数
    void setInterestRate(double arg)
    {
        _interestRate = arg;
    }
    
    double getInterestRate()
    {
        return _interestRate;
    }
};