处理概括关系之六 :Extract Subclass(提炼子类)

class 中的某些特性(features)只被某些(而非全部)实体(instances)用到。

新建一个subclass ,将上面所说的那一部分特性移到subclass 中。

动机(Motivation)

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

Extract Class 是Extract Subclass 之外的另一种选择,两者之间的抉择其实就是委托(delegation)和继承(inheritance)之间的抉择。Extract Subclass 通常更容易进行,但它也有限制:一旦对象创建完成,你无法再改变「与型别相关的行为」(class-based behavior )。但如果使用Extract Class ,你只需插入另一个不同组件( plugging in different components)就可以改变对象的行为。此外,subclasses 只能用以表现一组变化(one set of variations)。如果你希望class 以数种不同的方式变化,就必须使用委托(delegation)。

作法(Mechanics)

·为source class 定义一个新的subclass 。

·为这个新的subclass 提供构造函数。

Ø简单的作法是:让subclass 构造函数接受与superclass 构造函数相同的参数,并通过super 调用superclass 构造函数。

Ø如果你希望对用户隐藏subclass 的存在,可使用Replace Constructor with Factory Method。

·找出调用superclass 构造函数的所有地点。如果它们需要的是新建的subclass , 令它们改而调用新构造函数。

Ø如果subclass 构造函数需要的参数和superclass 构造函数的参数不同,可以使用Rename Method 修改其参数列。如果subclass 构造函数不需要superclass 构造函数的某些参数,可以使用Rename Method 将它们去除。

Ø如果不再需要直接实体化(具现化,instantiated)superclass ,就将它声明为抽象类。

·逐一使用Push Down Method 和 Push Down Field 将source class 的特性移到subclass 去。

Ø和Extract Class 不同的是,先处理函数再处理数据,通常会简单一些。

Ø当一个public 函数被下移到subclass 后,你可能需要重新定义该函数的调用端的局部变量或参数型别,让它们改调用subclass 中的新函数。如果忘记进行这一步骤,编译器会提醒你。

·找到所有这样的值域:它们所传达的信息如今可由继承体系自身传达(这一类值域通常是boolean 变量或type code )。以 Self Encapsulate Field 避免直接使用这些值域,然后将它们的取值函数(getter)替换为多态常量函数(polymorphic constant methods)。所有使用这些值域的地方都应该以Replace Conditional with Polymorphism 重构。

Ø任何函数如果位于source class 之外,而又使用了上述值域的访问函数(accessors),考虑以 Move Method 将它移到source class 中, 然后再使用Replace Conditional with Polymorphism。

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

范例:(Example)

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

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 subclass,因为上述某些行为和数据只在labor (劳工) 情况下才需要。首先建立这样一个class:

class LaborItem extends JobItem {}

我需要为LaborItem 提供一个构造函数,因为JobItem 没有「无引数构造函数」 ( no-arg constructor)。我把superclass 构造函数的参数列拷贝过来:

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

       super (unitPrice, quantity, isLabor, employee);

   }

这就足以让新的subclass 通过编译了。但是这个构造函数会造成混淆:某些参数是LaborItem 所需要的,另一些不是。稍后我再来解决这个问题。

下一步是要找出对JobItem 构造函数的调用,并从中找出「可替换为LaborItem 构造函数」者。因此,下列语句:

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

就被修改为:

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

此时我尚未修改变量型别,只是修改了构造函数所属的class 。之所以这样做,是因为我希望只在必要地点才使用新型别。到目前为止,subclass 还没有专属接口,因 此我还不想宣布任何改变。

现在正是清理构造函数参数列的好时机。我将针对每个构造函数使用Rename Method。首先处理superclass 构造函数。我要新建一个构造函数,并把旧构造函数声明为protected (不能直接声明为private ,因为subclass 还需要它):

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 修改subclass 构造函数:

class LaborItem

   public LaborItem (int quantity, Employee employee) {

       super (0, quantity, true, employee);

   }

此时的我仍然暂时使用protected superclass 构造函数。

现在,我可以将JobItem 的特性向下搬移。先从函数幵始,我先运用 Push Down Method 对付getEmployee() 函数:

class LaborItem...

   public Employee getEmployee() {

       return _employee;

   }

class JobItem...

  protected Employee _employee;

因为_employee 值域也将在稍后被下移到LaborItem ,所以我现在先将它声明为protected。

将_employee 值域声明protected 之后,我可以再次清理构造函数,让_employee 只在「即将去达的subclass 中」被初始化:

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,然后再修改访问函数(accessors),改用多态常量函数。所谓「多态常量函数」会在不同的subclass 实现版本中返回不同的固定值:

class JobItem...

   protected boolean isLabor() {

       return false;

   }

class LaborItem...

   protected boolean isLabor() {

       return true;

   }

然后,我就可以摆脱_isLabor 值域了。

现在,我可以观察isLabor() 函数的用户,并运用Replace Conditional with Polymorphism 重构它们。我找到了下列这样的函数:

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();

   }

当使用某项值域的函数全被下移至subclass 后,我就可以使用 Push Down Field 将值域也下移。如果尚还无法移动值域,那就表示,我需要对函数做更多处理,可能需要实施Push Down Method 或 Replace Conditional with Polymorphism。

由于_unitPrice 值域只被LaborItem 以外的对象(也就是parts job items)所用, 所以我可以再次运用Extract Subclass 对JobItem 提炼出一个subclass :PartsItem 。完成后,我可以将JobItem 声明为抽象类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值