处理概括关系之七 :Extract Superclass(提炼超类)

来源:互联网 发布:詹姆斯巅峰数据 编辑:程序博客网 时间:2024/05/19 22:44

两个classes 有相似特性(similar features)。

为这两个classes 建立一个superclass ,将相同特性移至superclass 。

动机(Motivation)

重复代码是系统中最主要的一种糟糕东西。如果你在不同的地方进行相同一件事 情,一旦需要修改那些动作时,你就得负担比你原本应该负担的更多事情。

重复代码的某种形式就是:两个classes 以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承机制。但是,在建立这些具有共通性的classes 之前,你往往无法发现这样的共通性,因此你经常会在「具有共通性」的classes 存在之后,再幵始建立其间的继承结构。

另一种选择就是Extract Class。这两种方案之间的选择其实就是继承(Inheritance )和委托(delegation)之间的选择。如果两个classes 可以共享行为, 也可以共享接口,那么继承是比较简单的作法。如果你选错了,也总有 Replace Inheritance with Delegation 这瓶后悔药可吃。

作法(Mechanics)

·为原本的classes 新建一个空白的abstract superclass。

·运用Pull Up Field, Pull Up Method, 和 Pull Up Constructor Body 逐一将subclass 的共同充素上移到superclass 。

Ø先搬移值域,通常比较简单。

Ø如果相应的subclass 函数有不同的签名式(signature),但用途相同,可以先使用Rename Method 将它们的签名式改为相同,然后 再使用 Pull Up Method。

Ø如果相应的subclass 函数有相同的签名式,但函数本体不同,可以在superclass 中把它们的共同签名式声明为抽象函数。

Ø如果相应的subclass 函数有不同的函数本体,但用途相同,可试着使用 Substitute Algorithm 把其中一个函数的函数本体拷贝到另一个函数中。如果运转正常,你就可以使用 Pull Up Method。

·每次上移后,编译并测试。

·检查留在subclass 中的函数,看它们是否还有共通成分。如果有,可以使用Extract Method 将共通部分再提炼出来,然后使用 Pull Up Method 将提炼出的函数上移到superclass 。如果各个subclass 中某个函数的整体流程很相似,你也许可以使用Form Template Method。

·将所有共通元素都上移到superclass 之后,检查subclass 的所有用户。如果它们只使用共同接口,你就可以把它们所索求的对象型别改为superclass 。

范例:(Example)

下面例子中,我以Employee 表示「员工」,以Department 表示「部门」:

class Employee...

   public Employee (String name, String id, int annualCost) {

       _name = name;

       _id = id;

       _annualCost = annualCost;

   }

   public int getAnnualCost() {

       return _annualCost;

   }

   public String getId(){

       return _id;

   }

   public String getName() {

       return _name;

   }

   private String _name;

   private int _annualCost;

   private String _id;

public class Department...

   public Department (String name) {

       _name = name;

   }

   public int getTotalAnnualCost(){

       Enumeration e = getStaff();

       int result = 0;

       while (e.hasMoreElements()) {

           Employee each = (Employee) e.nextElement();

           result += each.getAnnualCost();

       }

       return result;

   }

   public int getHeadCount() {

        return _staff.size();

   }

   public Enumeration getStaff() {

       return _staff.elements();

   }

   public void addStaff(Employee arg) {

       _staff.addElement(arg);

   }

   public String getName() {

       return _name;

   }

   private String _name;

   private Vector _staff = new Vector();

这里有两处共同点。首先,员工和部门都有名称(names);其次,它们都有年度成本(annual costs),只不过计算方式略有不同。我要提炼出一个superclass ,用以包容这些共通特性。第一步是新建这个superclass ,并将现有的两个classes 定义为其subclasses:

abstract class Party {}

class Employee extends Party...

class Department extends Party...

然后我开始把特性上移至superclass 。先实施Pull Up Field 通常会比较简单:

class Party...

   protected String _name;

然后,我可以使用 Pull Up Method 把这个值域的取值函数(getter)也上移至superclass :

class Party {

   public String getName() {

       return _name;

   }

我通常会把这个值域声明为private 。不过,在此之前,我需要先使用Pull Up Constructor Body,这样才能对_name 正确赋值:

class Party...

   protected Party (String name) {

       _name = name;

   }

   private String _name;

class Employee...

   public Employee (String name, String id, int annualCost) {

       super (name);

       _id = id;

       _annualCost = annualCost;

   }

class Department...

   public Department (String name) {

       super (name);

   }

Department.getTotalAnnualCost() 和 Employee.getAnnualCost() 两个函数的用途相同,因此它们应该有相同的名称。我先运用 Rename Method 把它们的名称改为相同:

class Department extends Party {

   public int getAnnualCost(){

       Enumeration e = getStaff();

       int result = 0;

       while (e.hasMoreElements()) {

           Employee each = (Employee) e.nextElement();

           result += each.getAnnualCost();

       }

       return result;

   }

它们的函数本体仍然不同,因此我目前还无法使用 Pull Up Method。但是我 可以在superclass 中声明一个抽象函数:

   abstract public int getAnnualCost()

这一步修改完成后,我需要观察两个subclasses 的用户,看看是否可以改变它们转而使用新的superclass 。用户之一就是Department 自身,它保存了一个Employee 对象群集。Department .getAnnualCost() 只调用群集内的元素(对象)的getAnnualCost() 函数,而该函数此刻乃是在Party class 声明的:

class Department...

   public int getAnnualCost(){

       Enumeration e = getStaff();

       int result = 0;

       while (e.hasMoreElements()) {

           Party each = (Party) e.nextElement();

           result += each.getAnnualCost();

       }

       return result;

   }

这一行为暗示一种新的可能性:我可以用Composite 模式[Gang of Four] 来对待Department 和Employee ,这样就可以让一个Department 对象包容另—个Department 对象。这是一项新功能,所以这项修改严格来说不属于重构范围。如果用户恰好需要Composite 模式,我可以修改_staff 值域名字,使其更好地表现这一模式。这一修改还会带来其他相应修改:修改addStaff() 函数名称,并将该函数的参数型别改为Party class 。最后还需要把headCount() 函数变成一个递归调用。我的作法是在Employee 中建立一个headCount() 函数,让它返回1;再使用Substitute Algorithm 修改Department 的headCount() 函数,让它总和(add)各部门的headCount() 调用结果。