小酌重构系列[1]——移动方法
概要
有些开发者在编写方法时,可能较少地去思考一个问题:方法放在这个class中是否合适?
他们可能会觉得:这个方法已经实现xxx功能了,放在哪个class都一样的,class不就是一个装方法的容器嘛。
我赞同class是一个装东西的容器,且不仅限于方法。
但是,容器是有区别的。本文要讲的“移动方法”,是一种让方法放进合适的class的重构策略。
选择合适的容器
生活中我们会用到杯子和箱子,杯子和箱子都是容器。
倘若你用杯子装书,用箱子装水,会产生不好的结果——杯子里放不下书,水装进箱子后,会打湿箱子。
// 杯子
public class Cup
{
// 装书
public void HoldBook()
{
}
}
// 箱子
public class Box
{
// 装水
public void HoldWater()
{
}
}
按照生活常识,我们应该用杯子装水,用箱子装书。
// 杯子
public class Cup
{
// 装水
public void HoldWater()
{
}
}
// 箱子
public class Box
{
// 装书
public void HoldBook()
{
}
}
每个程序员在开发完功能后,都应该回头读一读自己的代码,确认是否存在一些“牛头不对马嘴”的方法。
class是方法的容器,我们应该为每个方法寻找最合适的容器。
移动方法
现在引入本文的主题:“移动方法”。
该定义中有两个关键词:1. 适用 2. 语义。
“适用性”是指方法实现的功能应该适用它的class,“语义”是指方法的所描述的功能和class的语义是一致的。
“语义”的重要性高于“适用性”,例如:在程序中定义的扩展方法或者帮助类,通常都是被其它class调用的。这种情况下,我们应该着重体现“语义”。
有些开发者在编写Excel工具类时,在ExcelUtil中出定义了ExportCsv()这样的方法,尽管导出的Csv格式是可以用Excel打开的。
较好的做法是,新建一个CsvUtil类,将ExportCsv()方法定义在其中,下图阐述了这个重构过程(蓝色表示重构前,红色表示重构后)。
虽然多了一个class,但这使得Excel工具类和Csv工具类的语义更佳精确,代码的可读性也提高了。
示例
这段代码定义了两个类:BankAccount、AccountInterest,分别表示银行账户和账户利率。
计算利率的方法CalculateInterestRate()定义在BankAccount类。
namespace MoveMethod.Before
{
/// <summary>
/// 银行账户
/// </summary>
public class BankAccount
{
public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest)
{
AccountAge = accountAge;
CreditScore = creditScore;
AccountInterest = accountInterest;
}
public int AccountAge { get; private set; }
public int CreditScore { get; private set; }
public AccountInterest AccountInterest { get; private set; }
// 计算利率
public double CalculateInterestRate()
{
if (CreditScore > 800)
return 0.02;
if (AccountAge > 10)
return 0.03;
return 0.05;
}
}
/// <summary>
/// 账户利率
/// </summary>
public class AccountInterest
{
public BankAccount Account { get; private set; }
public AccountInterest(BankAccount account)
{
Account = account;
}
public double InterestRate
{
get { return Account.CalculateInterestRate(); }
}
public bool IntroductoryRate
{
get { return Account.CalculateInterestRate() < 0.05; }
}
}
}
这两个class用于描述一件客观事实——”银行账户和计算账户利率的方式“。
咋一看,这两个类没有什么问题。
但由于已经定义了AccountInterest类,这个class的语义是和利率相关的,而CalculateInterestRate()方法用于计算账户利率。
所以将CalculateInterestRate()方法放在BankAccount类中不太合适,应将其移动到AccountInterest类中。
namespace MoveMethod.After
{
/// <summary>
/// 银行账户
/// </summary>
public class BankAccount
{
public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest)
{
AccountAge = accountAge;
CreditScore = creditScore;
AccountInterest = accountInterest;
}
public int AccountAge { get; private set; }
public int CreditScore { get; private set; }
public AccountInterest AccountInterest { get; private set; }
}
/// <summary>
/// 账户利率
/// </summary>
public class AccountInterest
{
public BankAccount Account { get; private set; }
public AccountInterest(BankAccount account)
{
Account = account;
}
public double InterestRate
{
get { return CalculateInterestRate(); }
}
public bool IntroductoryRate
{
get { return CalculateInterestRate() < 0.05; }
}
/// <summary>
/// 计算利率
/// </summary>
public double CalculateInterestRate()
{
if (Account.CreditScore > 800)
return 0.02;
if (Account.AccountAge > 10)
return 0.03;
return 0.05;
}
}
}
下图描述了重构前后的区别(蓝色表示重构前,红色表示重构后)。
总结
移动方法是较简单的一种重构策略,它旨在将方法移动到合适的类。
这个策略的关键在于找到方法体现的行为(功能),以及该行为对应的语义。
找到语义,我们就能将它放到合适的类。
其中的难点也在于语义,语义应该具备“准确性”。
有时候业务知识、对现实物体(对象)的理解可能会阻碍我们寻找到准确的语义。
本文链接: 文章作者:keepfool 文章出处:http://www.cnblogs.com/keepfool/ 如果您觉得阅读本文对您有帮助,请点一右下角的“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎看官们转载,转载之后请给出作者和原文连接。

有些开发者在编写方法时,可能较少地去思考一个问题:方法放在这个class中是否合适?
他们可能会觉得:这个方法已经实现xxx功能了,放在哪个class都一样的,class不就是一个装方法的容器嘛。
我赞同class是一个装东西的容器,且不仅限于方法。
但是,容器是有区别的。本文要讲的“移动方法”可以让方法放进合适的class的一种重构策略。





浙公网安备 33010602011771号