重构手法之处理概括关系【3】
本小节目录
7Extract BaseClass(提炼基类)
概要
两个类有相似特性。为这两个类建立一个基类,将相同特性移至基类。
动机
重复代码是系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。
重复代码的某种形式就是:两个类以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承。但是,在建立这些具有共通性的类之前,你往往无法发现这样的共通性,因此常常会在具有共通性的类出现之后,再开始建立其间的继承结构。
范例
Dog类中的EatFood和Groom有可能被其他类用到,因为他们都是动物的一些公有性质,所以这个时候我们就会考虑对它进行提炼。
public class Dog { public void EatFood() { // eat some food } public void Groom() { // perform grooming } }
代码如下所示,提取了Animal方法来封装公用的EatFood和Groom类,从而使其他继承了Animal类的子类都可以使用这两个方法了。
public class Animal { public void EatFood() { // eat some food } public void Groom() { // perform grooming } } public class Dog : Animal { }
小结
这个重构是典型的继承用法,很多程序员都会选择这样做,但是要注意正确的使用,不要造成过度使用了继承,如果过度使用了,请考虑用接口、组合和聚合来实现。
这个手法也经常用到,比如做的MVC项目中的BaseController。
8Extract Interface(提炼接口)
概要
若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。将相同的子集提炼到一个独立接口中。
动机
如果某个类在不同环境下扮演截然不同的角色,使用接口就是个好主意。你可以针对每个角色以Extract Interface提炼出相应接口。另一种可以用Extract Interface的情况是:你想要描述一个类的外部依赖接口(outbound interface,即这个类要求服务提供方提供的操作)。如果你打算将来加入其它种类的服务对象。只需要求它们实现这个接口即可。
范例
TimeSheet类表示员工为客户工作的时间表,从中可以计算客户应该支付的费用。为了计算这些费用,TimeSheet需要知道员工级别,以及该员工是否具有特殊技能:
class TimeSheet { public double Charge(Employee emp, int days) { int baseCharge = emp.GetRate() * days; if (emp.HasSpecialSkill()) { return baseCharge * 1.05; } return baseCharge; } } class Employee { public int GetRate() { return 2; } public bool HasSpecialSkill() { return true; } }
除了提供员工的级别和特殊技能信息之外,Employee还有很多其他方面的功能,但本应用程序只需这两项功能。可以针对这两项功能定义一个接口,从而强调“我只需要这部分功能”的事实:
public interface IBillable { int GetRate(); bool HasSpecialSkill(); }
然后,声明让Employee实现这个接口:
class Employee : IBillable { public int GetRate() { return 2; } public bool HasSpecialSkill() { return true; } }
完成以后,修改TimeSheet类中Charge()函数声明,强调该函数只使用Employee的这一部分行为:
class TimeSheet { public double Charge(IBillable emp, int days) { int baseCharge = emp.GetRate() * days; if (emp.HasSpecialSkill()) { return baseCharge * 1.05; } return baseCharge; } }
到目前为止,我们只不过是在文档化方面有一点收获。但就这一个函数而言,这样的收获并没有太大的价值;但如有若干个类都使用IBillable接口,它就会很有用。如果我还想计算电脑租金,巨大的收获就显露出来了;要想计算客户租用电脑的费用,只需让Computer类实现IBillable接口,然后就可以把租用电脑的时间也填到时间表上了。
小结
这个重构策略也是一个常见的运用,很多设计模式也会在其中运用此思想。
9Collapse Hierarchy(折叠继承体系)
概要
基类和子类之间无太大区别。将它们合为一体。
动机
如果你曾经编写过继承体系,就会知道,继承体系很容易变得过分复杂。所谓重构继承体系,往往是将函数和字段在体系中上下移到。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把基类和子类合并起来。
范例
如下代码所示,StudentWebSite子类除了有一个属性用来说明网站是否是活动的外没有别的责任,在这种情形下我们意识到IsActive属性可以应用到所有的网站,所以我们可以将IsActive属性上移到基类中,并去掉StudentWebSite类。
public class Website { public string Title { get; set; } public string Description { get; set; } public IEnumerable<Webpage> Pages { get; set; } } public class StudentWebsite : Website { public bool IsActive { get; set; } } public class Webpage { }
重构后的代码如下:
public class Website { public string Title { get; set; } public string Description { get; set; } public IEnumerable<Webpage> Pages { get; set; } public bool IsActive { get; set; } } public class Webpage { }
小结
这项重构和前几篇最主要论述了子类和父类的继承关系以及如何判断什么时候需要使用继承,一般我们都能处理好这些关系,所以相对比较简单。
To Be Continued……