Introduce Local Extension (引入本地扩展)

Summary:

你须要为服务类提供一些额外函数,但你没法修改这个类。创建一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。 java

Motivation: 

在使用一些没法修改源码的类时,须要添加一些原类没有提供的函数,而这些函数又比较多,那么咱们须要将这些函数组织在一块儿放到恰当的地方。要达到这一目的,有两种标准对象技术能够用—子类化(subclassing)和包装(wrapping),这种状况下,咱们把子类和包装类统称为本地扩展(local extension)。 数组

使用本地扩展,使得“函数和数据应该被统一封装”这一原则得以坚持。若是一直把这些本该放在扩展类中的代码零散的置于其余函数中,最终只会让其余这些类变得过度复杂,并是的其中的函数难以复用。 app

在子类和包装类之间作选择时,首选子类,由于这样的工做量比较少。使用子类最大的障碍在于,它必须在对象建立期实施。若是咱们能够接管对象建立过程,那固然没问题;但若是想在对象建立以后再使用本地扩展,就有问题了。此外,子类化方案还必须产生一个子类对象,这种状况下,若是有其余对象引用了旧对象,咱们就同时有两个对象保存了原数据!若是原数据是不可修改的,那也没问题,咱们能够放心进行复制;但若是原数据容许被修改,问题就来了,由于一个修改动做没法同时改变两份副本。这是咱们就必须改用包装类。使用包装类时,对本地扩展的修改会搏击原对象,反之亦然。 函数

Mechanics:

 1.创建一个扩展类,将它做为原始类的子类或包装类。 this

2.在扩展类中加入转型构造函数。 spa

所谓“转型构造函数”是指“接受原对象做为参数”的构造函数。若是采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;若是采用包装类方案,那么转型构造函数应该将它获得的传入参数以实例变量的形式保存起来,用做接受委托的原对象。 code

3.在扩展类中加入新特性。 对象

4.根据须要,将原对象替换为扩展对象。 get

5.将针对原始类定义的全部外加函数搬移到扩展类中。 源码

范例

以java1.0.1的Date类为例。第一项待解决事项就是:使用子类仍是包装类。子类化是比较显而易见的办法:

class myDateSub extends Date{
     public myDateSub nextDay() ...
     public int dayOfYear() ...
}

包装类则需用上委托:

class myDateWrap{
      private Date original;
}
范例1:使用子类

        首先,创建一个myDateSub类表示“日期”,并使其成为Date的子类:

class myDateSub extends Date

而后,处理Date和扩展类的不一样处。MyDateSub构造函数须要委托给Date构造函数:

public MyDateSub(String dateString)
{
     super(dateString);
}

如今,加入一个转型构造函数,其参数是一个源类对象:

public MyDateSub(Date arg)
{
      super(arg.getTime);
}

如今,在扩展类中添加新特性,并使用Move Method将全部外加函数搬移到扩展类。因而下面代码:

client class...
     private static Date nextDay(Date arg)
     {
         //foreign method, should be on Date
          return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1)
     }

通过搬移以后,就成了:

class myDateSub...
     Date nextDay()
     {
         return new Date(getYear(),getMonth(),getDate()+1)
     }
范例2: 使用包装类

首先声明一个包装类:

class MyDateWrap{
    private Date original;
}

使用包专类方案是,对构造函数的设定与先前有所不一样。如今的构造函数将执行一个单纯的委托动做

public MyDateWrap(String dateString)
{
   original = new Date(dateString);
}

而转型构造函数则只是对其实例变量赋值而已:

public MyDateWrap(Date arg)
{
    original = arg;
}

接下来是一项枯燥乏味的工做:为原始类的全部函数提供委托函数。

public int getYear()
{
  return original.getYear();
}
public boolean equals(Object arg)
{
    if(this == arg)
    {
       return true;
    }
    if(!(arg instanceof MyDateWrap))
    {
       return false;
    }
    MyDateWrap other = ((MyDateWrap)arg);
    return (original.equale(other.original));
}

完成这项工做后,就能够使用Move Method将日期相关行为搬移到新类中。因而一下代码:

client class...
   private static Date nextDay(Date arg)
   {
      // foreign method, should be on Date
      return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
   }

通过搬移以后,就成了:

class MyDateWrap...
   Date nextDay
   {
     return new Date(getYear(),getMonth(),getDate()+1);
   }

使用包装类有一个特殊问题:如何处理“接受原始类之实例为参数”的函数。例如:

public boolean after(Date arg)

因为没法改变原始类,因此咱们只能作到在一个方向上的兼容--包装类的after() 函数能够接受包装类或原始类的对象;但原始类只能接受原始类的对象,不接受包装类对象。

这样覆写的目的是为了向用户隐藏包装类的存在。可是咱们没法彻底隐藏包装类的存在,由于某些系统所提供的函数(例如equals())会出问题。

在这种状况下,咱们只能向用户公开“我进行了包装”这一事实。咱们能够以一个新函数来进行日期之间的相等性检查:

public boolean equalsDate(Date arg);

能够重载equalsDate(),让一个重载版本接受Date对象,另外一个重载版本接受MyDateWrap对象。这样就没必要检查未知对象的类型了:

public boolean quealsDate(MyDateWrap arg);

子类化方案中就没有这样的问题,只要不覆写源函数就好了。

相关文章
相关标签/搜索