重构类关系-Extract Subclass提炼子类六

重构类关系-Extract Subclass提炼子类六

1.提炼子类

1.1.使用场景

类中的某些特性只被某些(而非全部)实例用到。新建一个子类,将上面所说的那一部分特性移到子类中

使用Extract Subclass (330)的主要动机是:你发现类中的某些行为只被一部分实例用到,其他实例不需要它们。有时候这种行为上的差异是通过类型码区分的,此时你可以使用Replace Type Code with Subclasses (223)或Replace Type Code with State/Strategy (227)。但是,并非一定要出现了类型码才表示需要考虑使用子类。

Extract Class (149)是Extract Subclass (330)之外的另一种选择,两者之间的抉择其实就是委托和继承之间的抉择。
Extract Subclass (330)通常更容易进行,但它也有限制:一旦对象创建完成,你无法再改变与类型相关的行为。但如果使用Extract Class (149),你只需插入另一个组件就可以改变对象的行为。此外,子类只能用以表现一组变化。如果你希望一个类以几种不同的方式变化,就必须使用委托。

1.2.如何做

  • 为源类定义一个新的子类。
  • 为这个新的子类提供构造函数。
  • 简单的做法是:让子类构造函数接受与超类构造函数相同的参数,并通过super调用超类构造函数。
  • 如果你希望对用户隐藏子类的存在,可使用Replace Constructor with Factory Method (304)。
  • 找出调用超类构造函数的所有地点。如果它们需要的是新建的子类,令它们改而调用新构造函数。
  • 如果子类构造函数需要的参数和超类构造函数的参数不同,可以使用Rename Method (273)修改其参数列。如果子类构造函数不需要超类构造函数的某些参数,可以使用Rename Method (273)将它们去除。
  • 如果不再需要直接创建超类的实例,就将超类声明为抽象类。
  • 逐一使用Push Down Method (328)和Push Down Field (329)将源类的特性移到子类去。
  • 和Extract Class (149)不同的是,先处理函数再处理数据,通常会简单一些。当一个public函数被下移到子类后,你可能需要
    新定义该函数的调用端的局部变量或参数类型,让它们改而调用子类中的新函数。如果忘记进行这一步骤,编译器会提醒你。
  • 找到所有这样的字段:它们所传达的信息如今可由继承体系自身传达(这一类字段通常是boolean变量或类型码)。以Self Encapsulate Field (171)避免直接使用这些字段,然后将它们的取值函数替换为多态常量函数。所有使用这些字段的地方都应该以Replace Conditional with Polymorphism (255)重构。
  • 任何函数如果位于源类之外,而又使用了上述字段的访问函数,考虑以Move Method (142)将它移到源类中,然后再使用Replace Conditional with Polymorphism (255)。
  • 每次下移之后,编译并测试。

1.3.示例

下面是JobItem 类,用来决定当地修车厂的工作报价

// 工作报价类
 class JobItem ...
   public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
    
    
       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
       _employee = employee;
   }
   public int getTotalPrice() {
    
    
       return getUnitPrice() * _quantity;
   }
   public int getUnitPrice(){
    
    
       return (_isLabor) ?
            _employee.getRate():
            _unitPrice;
   }
   public int getQuantity(){
    
    
       return _quantity;
   }
   public Employee getEmployee() {
    
    
       return _employee;
   }
   private int _unitPrice;
   private int _quantity;
   private Employee _employee;
   private boolean _isLabor;

	// 员工类
 class Employee...
   public Employee (int rate) {
    
    
       _rate = rate;
   }
   public int getRate() {
    
    
       return _rate;
   }
   private int _rate;

我要提炼出一个LaborItem子类,因为上述某些行为和数据只在按工时(labor)收费的情况下才需要。首先建立这样一个类:

 class LaborItem extends JobItem {
    
    }

我需要为LaborItem提供一个构造函数,因为JobItem没有默认构造函数。我把超类构造函数的参数列复制过来:

   public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
    
    
       super (unitPrice, quantity, isLabor, employee);
   }

这就足以让新的子类通过编译了。但是这个构造函数会造成混淆:某些参数是LaborItem所需要的,另一些不是。稍后我再来解决这个问题。
下一步是要找出对JobItem构造函数的调用,并从中找出可以改用LaborItem构造函数的地方。因此,下列语句:

JobItem j1 = new JobItem (0, 5, true, kent);

就被修改为

JobItem j1 = new LaborItem (0, 5, true, kent);

此时我尚未修改变量类型,只是修改了构造函数所属的类。之所以这样做,是因为我希望只在必要地点才使用新类型。到目前为止,子类还没有专属接口,因此我还不想宣布任何改变。
现在正是清理构造函数参数列的好时机。我将针对每个构造函数使用Rename Method (273)。首先处理超类构造函数。我要新建一个构造函数,并把旧构造函数声明为protected(不能直接声明为private,因为子类还需要它):

 class JobItem...
  protected JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
    
    
       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
       _employee = employee;
   }
   public JobItem (int unitPrice, int quantity) {
    
    
       this (unitPrice, quantity, false, null)
   }

现在,外部调用应该使用新构造函数

JobItem j2 = new JobItem (10, 15);

编译、测试都通过后,我再使用Rename Method (273)修改子类构造函数

 class LaborItem
   public LaborItem (int quantity, Employee employee) {
    
    
       super (0, quantity, true, employee);
   }

此时我仍然暂时使用protected的超类构造函数。
现在,我可以将JobItem的特性向下搬移。先从函数开始,我先运用Push Down Method (328)对付getEmployee()函数:

 class LaborItem...
   public Employee getEmployee() {
    
    
       return _employee;
   }
 class JobItem...
  protected Employee _employee;

因为_employee字段也将在稍后被下移到LaborItem,所以我现在先将它声明为protected。
将_employee字段声明为protected之后,我可以再次清理构造函数,让_employee只在即将去达的子类中被初始化:

 class JobItem...
   protected JobItem (int unitPrice, int quantity, boolean isLabor) {
    
    
       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
   }
 class LaborItem ...
   public LaborItem (int quantity, Employee employee) {
    
    
       super (0, quantity, true);
        _employee = employee;
   }

_isLabor字段所传达的信息,现在已经成为继承体系的内在信息,因此我可以移除这个字段了。最好的方式是:先使用Self Encapsulate Field (171),然后再修改访问函数,改用多态常量函数——这样的函数会在不同的子类实现版本中返回不同的固定值:

 class JobItem...
   protected boolean isLabor() {
    
    
       return false;
   }
 class LaborItem...
   protected boolean isLabor() {
    
    
       return true;
   }

然后,我就可以摆脱_isLabor字段了。
现在,我可以观察isLabor()函数的用户,并运用Replace Conditional with Polymorphism (255)重构它们。我找到了下列这样的函数

 class JobItem...
   public int getUnitPrice(){
    
    
       return (isLabor()) ?
            _employee.getRate():
            _unitPrice;
   }

将它重构为:

 class JobItem...
   public int getUnitPrice(){
    
    
       return _unitPrice;
   }
 class LaborItem...
   public int getUnitPrice(){
    
    
       return  _employee.getRate();
   }

当使用某项字段的函数全被下移至子类后,我就可以使用Push Down Field (329)将字段也下移。如果尚无法移动字段,那就表示我需要对函数做更多处理,可能需要实施Push Down Method (328)或Replace Conditional with Polymorphism (255)。
由于只有按零件收费的工作项才会用到_unitPrice字段,所以我可以再次运用Extract Subclass (330)对JobItem提炼出一个子类:PartsItem。完成后,我可以将JobItem声明为抽象类。

猜你喜欢

转载自blog.csdn.net/m0_38039437/article/details/129753952
今日推荐