设计模式(六)--适配器模式
适配器模式和装饰器模式在一定程度上有相似的地方,LZ决定把它们放到一起记忆,这篇先看下适配器模式,这个模式相对重要,在实际编码过程中经常用到。
适配器模式从实现方式上分为两种,类适配器和对象适配器,这两种的区别在于实现方式上的不同,类适配器采用继承,对象适配器采用组合的方式。
另外从使用目的上来说,也可以分为两种,特殊适配器和缺省适配器,这两种的区别在于使用目的上的不同,一种为了复用原有的代码并适配当前的接口,一种为了提供缺省的实现,避免子类需要实现不该实现的方法。
首先应该明白一点,适配器模式是补救措施,所有在系统设计过程中请忘记这个设计模式,这个模式只是在你无可奈何时的补救方式。
那么我们什么时候使用这个模式呢?场景通常情况下是,系统中有一套完整的类结构,而我们需要利用其中某一个类的功能(通俗点说可以说是方法),但是我们的客户端只认识另外一个和这个类结构不相关的接口,这时候就是适配器模式发挥的时候了,我们可以将这个现有的类与我们的目标接口进行适配,最终获得一个符合需要的接口并且包含待复用的类的功能的类。
接下来我们举一个例子,比如我们在观察者一章中就提到一个问题,就是说观察者模式的一个缺点,即如果一个现有的类没有实现Observer接口,那么我们就无法将这个类作为观察者加入到被观察者的观察者列表中,这实在太遗憾了。
在这个问题中,我们需要得到一个Observer类的接口,但是又想用原有的类的功能,但是我们又改不了这个原来的类的代码,那么适配器模式就是你的不二选择了。
我们举个具体的例子,比如我们希望将HashMap这个类加到观察者列表中,在被观察者产生变化时,假如我们要清空整个Map。但是现在加不进去,为什么呢?
因为Observable的观察者列表只认识Observer这个接口,它不认识HashMap,怎么办呢?
这种情况下,我们就可以使用类适配器的方式将我们的HashMap做点手脚,刚才说过了,类适配器是采用继承的方式,那么我们写出如下适配器。
public class HashMapObserverAdapter<K, V> extends HashMap<K, V> implements Observer{ public void update(Observable o, Object arg) { //被观察者变化时,清空Map super.clear(); } }
即我们继承我们希望复用其功能的类,并且实现我们想适配的接口,在这里就是Observer,那么就会产生一个适配器,这个适配器具有HashMap的功能,又实现了观察者接口,所以这个适配器现在可以加入到观察者列表里了。
类适配器挺简单的,下面我们看看对象适配器,刚才说了对象适配器是采用组合的方式实现。
为什么要采用组合呢?继承的方式不是很好吗?
究其根本,是因为java单继承的原因,一个java类只能有一个父类,所以当我们要适配的对象是两个类的时候,就没办法用继承的方式了。
我们还是拿观察者模式的例子来说,比如我们现在有一个写好的类,假设就是个实体类吧,如下。
public class User extends BaseEntity{ private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
我们的实体类大部分都是继承自BaseEntity的,如果User想成为被观察者,就需要继承Observable类,类适配器显然不适合了。对象适配器就该上场了。
//我们继承User,组合Observable. public class ObservableUser extends User{ private Observable observable = new Observable(); public synchronized void addObserver(Observer o) { observable.addObserver(o); } public synchronized void deleteObserver(Observer o) { observable.deleteObserver(o); } public void notifyObservers() { observable.notifyObservers(); } public void notifyObservers(Object arg) { observable.notifyObservers(arg); } public synchronized void deleteObservers() { observable.deleteObservers(); } protected synchronized void setChanged() { observable.setChanged(); } protected synchronized void clearChanged() { observable.clearChanged(); } public synchronized boolean hasChanged() { return observable.hasChanged(); } public synchronized int countObservers() { return observable.countObservers(); } }
我们继承User,而不是继承Observable,这个原因刚才已经说过了,我们不能破坏项目中的继承体系,所以现在可观察的User(ObservableUser)依然处于我们实体的继承体系中,另外如果想让ObservableUser具有User的属性,则需要将User的属性改为protected。
这下好了,我们有了可观察的User了,但是这样做不好,为什么?第一个代码里所有用到可观察User的地方,我们都要把User改为ObservableUser。第二个我们要是还有Person,Employee类都要具有可观察功能的话,那又要新增两个类,代码又基本相似。我们应该从BaseEntity下手,新增可观察基类。
//我们扩展BaseEntity,适配出来一个可观察的实体基类 public class BaseObservableEntity extends BaseEntity{ private Observable observable = new Observable(); public synchronized void addObserver(Observer o) { observable.addObserver(o); } public synchronized void deleteObserver(Observer o) { observable.deleteObserver(o); } public void notifyObservers() { observable.notifyObservers(); } public void notifyObservers(Object arg) { observable.notifyObservers(arg); } public synchronized void deleteObservers() { observable.deleteObservers(); } protected synchronized void setChanged() { observable.setChanged(); } protected synchronized void clearChanged() { observable.clearChanged(); } public synchronized boolean hasChanged() { return observable.hasChanged(); } public synchronized int countObservers() { return observable.countObservers(); } }
这下好了,谁想具有被观察者的功能,就改去继承我们适配好的BaseObservableEntity就好了,User之上的代码就不用动了。
以上两种方式都是为了复用现有的代码而采用的适配器模式,LZ刚才说了,根据目的不同,适配器模式也可以分成两种,类适配器和对象适配器属于定制适配器,基本上每次都要新建一个类。还有另外一种称为缺省适配器。
首先我们得先说下缺省适配器为什么要出现,因为适配器模式大部分情况下是为了补救,既然是补救,那么肯定是历史原因造成的我们需要使用这个模式。
比如我们有这样一个类。
public interface Person { void speak(); void listen(); void work(); }
这是一个人的接口,这个接口表示了人可以说话,听和工作,但是并不是所有的人都具有这三个方法。比如在校的学生就没有work方法,真实项目中如果遇到类似的情况,那怎么办呢?
这时候缺省适配器就可以发挥作用了。
public class DefaultPerson implements Person{ public void speak() { } public void listen() { } public void work() { } }
我们创造一个Person接口的默认实现,它里面都是一些默认的方法,当然这里因为没什么可写就空着了,实际当中可能会加入一些默认情况下的操作,比如如果方法返回结果整数,那么我们在缺省适配器里可以默认返回0。
至此,适配器模式就差不多了。