重构手法之处理概括关系【1】
本小节目录
1Pull Up Field(字段上移)
概要
两个子类拥有相同的字段。将该字段移至基类。
动机
如果各个子类是分别开发的,或者是在重构过程中组合起来的,你常常会发现它们拥有重复性,特别是字段容易重复。判断若干字段是否重复,唯一的办法就是观察函数如何使用它们。如果它们被使用的方式很相似,就可以将它们归纳到基类中去。
范例
如下代码所示,Employee的两个子类Salesman和Enginner都有_name字段,所以可以考虑把这个字段提到基类中。
class Employee { } class Salesman : Employee { private string _name; } class Enginner : Employee { private string _name; }
重构后的代码如下,这样提的前提是这些子类有一个基类或者有很多相似的字段和方法,不然为了一个字段而单独建立一个抽象类是不可取的,所以这个就需要具体权衡。
class Employee { protected string _name; } class Salesman : Employee { } class Enginner : Employee { }
小结
本项重构主要是减少重复:首先它去除了重复的数据声明;其次它使你可以将该字段的行为从子类移至基类,从而去除重复的行为。
2Pull Up Method(函数上移)
概要
有些函数,在各个子类中产生完全相同的结果。将该函数移至基类。
动机
避免行为重复是很重要的。尽管重复的两个函数也可以各自工作得很好,但重复自身只会成为错误的滋生地,此外别无价值。无论何时,只要系统中出现重复,你就面临“修改其中一个却未能修改另一个”的风险。
使用本项重构的场合:(1)如果某个函数在各个子类中的函数体都相同;(2)子类的函数覆写了基类的函数,但却仍然做相同的工作。
范例
以Customer表示“顾客”,它有两个子类:表示“普通顾客”的RegularCustomer和表示“贵宾”的PreferredCustomer。
public abstract class Customer { protected DateTime _lastBillDate; public void AddBill(DateTime date, double amount) { } } class RegularCustomer : Customer { void CreateBill(DateTime date) { double chargeAmount = ChargeFor(_lastBillDate, date); AddBill(date, chargeAmount); } public double ChargeFor(DateTime start, DateTime end) { return 0; } } class PreferredCustomer : Customer { void CreateBill(DateTime date) { double chargeAmount = ChargeFor(_lastBillDate, date); AddBill(date, chargeAmount); } public double ChargeFor(DateTime start, DateTime end) { return 100; } }
两个子类中都有一个CreateBill()函数,并且代码完全一样,但我不能直接把这个函数上移到基类中,因为各个子类的ChargeFor()函数并不相同。必须先在基类中声明一个ChargeFor()抽象函数:
public abstract class Customer { protected DateTime _lastBillDate; protected void AddBill(DateTime date, double amount) { } protected void CreateBill(DateTime date) { double chargeAmount = ChargeFor(_lastBillDate, date); AddBill(date, chargeAmount); } public abstract double ChargeFor(DateTime start, DateTime end); } class RegularCustomer : Customer { public override double ChargeFor(DateTime start, DateTime end) { return 0; } } class PreferredCustomer : Customer { public override double ChargeFor(DateTime start, DateTime end) { return 100; } }
小结
这个重构要根据具体情况使用,如果不是每个子类都有这个方法的话,可以考虑使用接口或者其他方式。
3Pull Up Constructor Body(构造函数本体上移)
概要
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。
在基类中新建一个构造函数,并在子类构造函数中调用它。
动机
如果你看见各个子类中的函数有共同行为,第一个念头应该是将共同行为提炼到一个独立函数中,然后将这个函数提升到基类。对于构造函数而言,它们彼此的共同行为往往就是“对象的建构”。这时候你需要在基类中提供一个构造函数,然后让子类都来调用它。
范例
class Employee { protected string _name; protected string _id; } class Manager:Employee { private int _grade; public Manager(string name,string id,int grade) { _name = name; _id = id; _grade = grade; } }
Employee的字段应该在Employee构造函数中设值。因此定义了一个Employee构造函数,并将它声明为Protected,表示子类应该调用它:
class Employee { protected string _name; protected string _id; protected Employee(string name, string id) { _name = name; _id = id; } } class Manager : Employee { private int _grade; public Manager(string name, string id, int grade) : base(name, id) { _grade = grade; } }
小结
这个重构手法和提升字段、提升方法很相似。只不过是将“对象的建构”提升到基类中。
To Be Continued……