在类与类之间搬移状态和行为,是重构过程中必不可少的步骤。很有可能在你现在觉得正常的类,等你到了下个礼拜你就会觉得不合适。或者你在下个礼拜创建了一个新的类并且你需要讲现在类的部分字段和行为移动到这个新类中。如果你发现在一个类中的某个字段,更多的被别的类的函数所使用,包括设值set和取值get函数锁取用,那么你就应该考虑搬移这个字段。当然,你也可能会去考虑是否使用Move Method去搬移这些使用这个字段的函数到这个字段的源类中去,这取决于你是否能够接受接口的变化,如果这些函数更加适合待在原地不动,那么你就应该选择Move Field。如果你此时需要将行为和字段都搬移出去出去形成一个新类(Extract Class),那么此时你应该先Move Field之后再Move Method。
做法:
- 如果你需要搬移的字段是public级别的,你需要使用Encapsulate Field先将它封装起来。如果你可能需要移动那些频繁访问该字段的函数,或者有很多函数访问某个字段,那么Self Encapsulate Field可能对你有帮助。
- 编译,测试。
- 在目标类建立和源类相同的字段,并建立相应的设值和取值函数。
- 编译目标类。
- 决定如何在源对象中引用目标对象。首先查看是否有现成的字段或者函数可以取得目标对象,如果没有就尝试能不能建立一个这样的函数,如果还是不能,就考虑在源类新建一个字段来存放目标对象,这可能是永久性的修改,但也可能因为后续的重构你会把这个新建字段给删除。
- 删除源字段。
- 将所有对源字段的引用替换为对某个目标函数的调用。如果需要读取变量,就把对源字段的引用替换为对目标取值函数的调用,如果需要对变量赋值,就把对源字段的引用替换为对目标设值函数的调用。如果源字段不是private级别的,就必须在源类的所有子类中查找替换对源字段的引用,并进行替换。
- 编译,测试。
例子:
class Account...
private:
AccountType m_type;
double m_interestRate;
public:
double interestForAmountdays(double amount, int days)
{
return m_interestRate * amount * days / ;
}
我想把m_interestRate移到AccountType中去,我们可以看到,interestForAmountdays这个函数已经引用了这个字段,接下来我们要做的就是在AccountType中建立同样的字段,并设置取值和设值函数
class AccountType...
private:
double m_interestRate;
public:
void setInterestRate(double arg)
{
m_interestRate = arg;
}
double interestRate() const
{
return m_interestRate;
}
添加完成后,我们对目标类进行编译。现在我们需要将源类中对原字段访问的函数转而使用对目标取值函数的调用,然后我们删除源类中的字段。删除源字段也可以让编译器帮我们查找出所有需要修改的函数。
double interesetForAmountdays(double amount, int days)
{
return m_type.interestRate() * amount * days / ;
}
这里演示的是单一函数存在对源字段的引用。如果这里有很多函数都对原字段进行过引用,我们可以先使用Self Encapsulate Field进行自我封装。
class Account...
private:
AccountType m_type;
double m_interestRate;
public:
double interesetForAmountdays(double amount, int days)
{
return interestRate() * amount * days / ;
} void setInterestRate(double arg)
{
m_interestRate = arg;
} double interestRate() const
{
return m_interestRate;
}
这个带来的好处就是当我们搬移字段中之后,我们不需要去追踪所有引用字段的函数,我们只需要去修改取值函数和设值函数就可以达到所有函数的修改。
double interesetForAmountdays(double amount, int days)
{
return interestRate() * amount * days / ;
} void setInterestRate(double arg)
{
m_type.setInterestRate(arg);
}
double interestRate() const
{
return m_type.interestRate();
}
当然,如果你也可以让这些调用访问函数的用户直接去访问目标对象。Self Encapsulate Field可以让你的重构进行小步伐前进,很有帮助。小步伐前进也是重构能够稳步前进的秘诀,可以让你的工作和生活变得更加轻松。特别值得一提的是,如果你一开始就使用Self Encapsulate Field,如果此时你需要使用Move Method,在这个函数中可能正好有对该字段的访问
double interesetForAmountdays(double amount, int days)
{
return interestRate() * amount * days / ;
}
他在Account类中,被你搬移到了AccountType中,因为你没有在函数里写成字段,而是写的取值函数,这意味着你这段函数即时到了AccountType中你也无需修改,你只需要对AccountType增加对应的取值函数interestRate()即可完成Move Method。