继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

java设计模式-适配器模式

hydraWindy
关注TA
已关注
手记 27
粉丝 27
获赞 233

适配器模式从实现方式上分为两种,类适配器和对象适配器,这两种的区别在于实现方式上的不同,一种采用继承,一种采用组合的方式。
另外从使用目的上来说,也可以分为两种,特殊适配器和缺省适配器,一种为了复用原有的代码并适配当前的接口,一种为了提供缺省的实现,避免子类需要实现不该实现的方法。
通常的使用场景是,系统中有一套完整的类结构,而我们需要利用其中某一个类的功能(通俗点说可以说是方法),但是我们的客户端只认识另外一个和这个类结构不相关的接口,这时候就是适配器模式发挥的时候了,我们可以将这个现有的类与我们的目标接口进行适配,最终获得一个符合需要的接口并且包含待复用的类的功能的类。
举一个例子,比如观察者模式的一个缺点,即如果一个现有的类没有实现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();
    }

}

现在继承了我们希望复用其功能的类(即HashMap),并且实现了想适配的接口(即Observer),所以这个适配器现在可以加入到观察者列表了。
下面看看对象适配器,对象适配器是采用组合的方式实现。因为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的,要想具有被观察者的功能需要继承Observable类,但是java是单继承的,显然直接继承是不可能的。
或许可以让User不继承BaseEntity,把BaseEntity里面的东西全部挪动到User类,又或者不继承Observable了,把Observable里面的东西全部挪到User类里面。
这并不是不行,但是这是个很大的隐患,比如项目到时候要针对BaseEntity的子类进行扫描,用来做一些事情,这时候如果User没继承BaseEntity,那么就会遗漏掉这个类,这会破坏继承体系。
相反,如果你不继承Observable,那么你的User类看起来会非常杂乱,而且假设现在不仅User类可以被观察了,Person类,Employee都能被观察了,难道要把Observable的代码COPY三次到这三个类里面吗?
适配器模式就是为了帮助我们复用代码的,这里使用适配器模式就可以帮我们复用Observable的代码或者说功能。
做出如下适配器,这里采用的对象适配器:

public class ObservableNx extends Observable {

        /*Observable 的clearChanged和setChanged是protected属性,所以需要单独定义出来一个子类,调用父类的方法。
        并且notifyObservers方法传递的不是Observable 对象,所以对象发生改变时调用的方法应该为notifyObservers(Object arg),传递的数据以map的形式(或其他形式)存放在arg中*/

    @Override
    protected synchronized void clearChanged() {
        super.clearChanged();
    }

    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}
//我们继承User,组合Observable.
public class ObservableUser extends User{

    private ObservableNx observable = new ObservableNx();

    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 setChange(){
        observable.setChanged();
    }

    protected synchronized void clearChange(){
        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了,不过这里明显不是最好的解决方案。因为我们要是还有Person,Employee类都要具有可观察的功能的话,那其实也相当惨,因为下面那些Observable的方法我们还要再复制一遍。
最终相对来说比较好的解决方案就是定义如下可观察的基类:

public class ObservableNx extends Observable {

        /*原因同上*/
    @Override
    protected synchronized void clearChanged() {
        super.clearChanged();
    }

    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}
//我们扩展BaseEntity,适配出来一个可观察的实体基类
public class BaseObservableEntity extends BaseEntity{

    private ObservableNx observable = new ObservableNx();

    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,Person,Employee要是想具有可被观察的功能,那就改成继承适配好的BaseObservableEntity,而且由于BaseObservableEntity继承了BaseEntity,所以他们三个依然处于我们的实体继承体系中,而且由于BaseObservableEntity是新增的扩展基类,所以不会对原来的继承体系造成破坏。

适配器模式的用法还是比较清晰的,以上两种方式都是为了复用现有的代码而采用的适配器模式。另外根据目的的不同,适配器模式也可以分为两种,那么上述便是第一种,可称为定制适配器,还有另外一种称为缺省适配器

最小接口原则:接口的行为应该尽量的少。
如果不遵守这个原则,结果就是实现这个接口的子类出现很多空方法,而且根本用不上这个方法的情况。但由于JAVA语言规则的原因,实现一个接口必须实现它的全部方法,所以我们的子类不得不被迫写一堆空方法在那,只为了编译通过。
所以为了解决这一问题,缺省适配器就出现了。比如我们有如下接口:

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。
现在如果有一个乞丐,只要继承这个默认的适配器(DefaultPerson),然后覆盖掉speak和listen方法就可以,至于work,由于适配器提供了默认的实现,所以就不需要再写了。
因为接口设计的最小化只是理想状态,所以难免会有一些实现类,对其中某些方法不感兴趣。如果方法过多,子类也很多,并且子类的大部分方法都是空着的,那么就可以采取这种方式了。
当然,这样做违背了里氏替换原则,但是上面的做法原本就违背了接口的最小化原则,所以在真正使用时要权衡二者的利弊,到底需要的是什么。原则只是指导,并不一定也不可能全部满足,一定要学会取舍。
总结下两种实现方式的适配器所使用的场景,两者都是为了将已有类的代码复用并且适配到客户端需要的接口上去。
1:第一种类适配器,一般是针对适配目标是接口的情况下使用。
2:第二种对象适配器,一般是针对适配目标是类或者是需要复用的对象多于一个的时候使用,这里再专门提示一下,对象适配器有时候是为了将多个类一起适配,所以才不得不使用组合的方式,而且我们采用对象适配器的时候,继承也不是必须的,而是根据实际的类之间的关系来进行处理,上述例子当中一定要直接或间接的继承自BaseEntity是为了不破坏我们原来的继承体系,但有些情况下这并不是必须的。
3:对于第三个缺省适配器,一般是为了弥补接口过大所犯下的过错,但是也请注意衡量利弊,权衡好以后再考虑是否要使用缺省适配器。

打开App,阅读手记
2人推荐
发表评论
随时随地看视频慕课网APP