重构类关系-Extract Superclass提炼超类七

重构类关系-Extract Superclass提炼超类七

1.提炼超类

1.1.使用场景

“两个类有相似特性。
为这两个类建立一个超类,将相同特性移至超类。

重复代码是系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。

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

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

1.2.如何做

  • 为原本的类新建一个空白的抽象超类。
  • 运用Pull Up Field (320)、Pull Up Method (322)和Pull Up Constructor Body (325)逐一将子类的共同元素上移到超类。
  • 先搬移字段,通常比较简单。
  • 如果相应的子类函数有不同的签名,但用途相同,可以先使用Rename Method (273)将它们的签名改为相同,然后再使用Pull Up Method (322)。
  • 如果相应的子类函数有相同的签名,但函数本体不同,可以在超类中把它们的共同签名声明为抽象函数。
  • 如果相应的子类函数有不同的函数本体,但用途相同,可试着使用Substitute Algorithm (139)把其中一个函数的函数本体复制到另一个函数中。如果运转正常,你就可以使用Pull Up Method (322)。
  • 每次上移后,编译并测试。
  • 检查留在子类中的函数,看它们是否还有共通成分。如果有,可以使用Extract Method (110)将共通部分再提炼出来,然后使用Pull Up Method (322)将提炼出的函数上移到超类。如果各个子类中某个函数的整体流程很相似,你也许可以使用Form Template Method (345)。
  • 将所有共通元素都上移到超类之后,检查子类的所有用户。如果它们只使用共同接口,你就可以把它们请求的对象类型改为超类。

1.3.示例

下面例子中,我以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();

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

 abstract class Party {
    
    }
 class Employee extends Party...
 class Department extends Party...

然后我开始把特性上移至超类。先实施Pull up Field (320)通常会比较简单:

 class Party...
   protected String _name;

然后,我可以使用Pull Up Method (322)把这个字段的取值函数也上移至超类:

 class Party {
    
    
   public String getName() {
    
    
       return _name;
   }

我通常会把这个字段声明为private。不过,在此之前,我需要先使用Pull Up Constructor Body (325),这样才能对_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 (273)把它们的名称改为相同:

 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 (322)。但是我可以在超类中声明一个抽象函数:

扫描二维码关注公众号,回复: 14644384 查看本文章
   abstract public int getAnnualCost()

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

 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。最后还需要把headCount()函数变成一个递归调用。我的做法是在Employee中建立一个headCount()函数,让它返回1;再使用Substitute Algorithm (139)修改Department的headCount()函数,让它加总各部门的headCount()调用结果。

猜你喜欢

转载自blog.csdn.net/m0_38039437/article/details/129754820