(八)适配器模式

转载:http://www.cnblogs.com/zuoxiaolong/p/pattern9.html

适配器模式从实现方式上分为两种,类适配器和对象适配器,这两种的区别在于实现方式上的不同,一种采用继承,一种采用组合的方式。

       另外从使用目的上来说,也可以分为两种,特殊适配器和缺省适配器,这两种的区别在于使用目的上的不同,一种为了复用原有的代码并适配当前的接口,一种为了提供缺省的实现,避免子类需要实现不该实现的方法。

首先应该明白一点,适配器模式是补救措施,所以在系统设计过程中请忘掉这个设计模式,这个模式只是在你无可奈何时的补救方式。

那么我们什么时候使用这个模式呢?场景通常情况下是,系统中有一套完整的类结构,而我们需要利用其中某一个类的功能(通俗点说可以说是方法),但是我们的客户端只认识另外一个和这个类结构不相关的接口,这时候就是适配器模式发挥的时候了,我们可以将这个现有的类与我们的目标接口进行适配,最终获得一个符合需要的接口并且包含待复用的类的功能的类。

接下来我们举一个例子,比如我们在观察者一章中就提到一个问题,就是说观察者模式的一个缺点,即如果一个现有的类没有实现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类只能有一个父类,所以当我们要适配的对象是两个类的时候,你怎么办呢?你难道要将两个类全部写到extends后面吗,如果你这么做了,那么编译器会表示它的不满的。

我们还是拿观察者模式那一章的例子来说(观察者模式比较惨,老要适配器模式擦屁股),比如我们现在有一个写好的类,假设就是个实体类吧。如下。

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类,你说你怎么继承吧。

你是不是想说,那我的User不继承BaseEntity不就完事了,我把BaseEntity里面的东西全部挪动到User类,或者我不继承Observable了,把Observable里面的东西全部挪到User类里面。

这并不是不行,但是这是个很大的隐患,比如我们项目到时候要针对BaseEntity的子类进行扫描,用来做一些事情,这时候如果User没继承BaseEntity,那么你就会遗漏掉这个类,这会破坏你的继承体系,付出太大了。

相反,如果你不继承Observable,那么你的User类看起来会非常杂乱,而且假设我现在不仅User类可以被观察了,我的Person类,Employee都能被观察了,你难道要把Observable的代码COPY三次到这三个类里面吗?

不要忘了刚才说的,适配器模式就是为了帮助我们复用代码的,这里使用适配器模式就可以帮我们复用Observable的代码或者说功能。

基于上面LZ的讨论,我们做出如下适配器,这里采用的对象适配器。

//我们继承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了。不过LZ早就说过,设计模式要活用,这里明显不是最好的解决方案。因为我们要是还有Person,Employee类都要具有可观察的功能的话,那其实也相当惨,因为下面那些Observable的方法我们还要再复制一遍。

提示到这里,不知各位想到更好的解决方案了吗?尤其是新手可以好好思考下。

LZ这里给出最终相对来说比较好的解决方案,那就是我们定义如下可观察的基类。

//我们扩展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();
    }

}

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

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

首先我们得先说下缺省适配器为什么要出现,因为适配器模式大部分情况下是为了补救,所以既然补救,那么肯定是历史原因造成的我们需要使用这个模式。

我们来看看缺省适配器的历史来由,不知各位还是否记得在第一章总纲中,LZ曾经提到过一个原则,最小接口原则。

这个原则所表达的思想是说接口的行为应该尽量的少,那么还记得LZ当时说如果你没做到的话会产生什么情况吗?

结果就是实现这个接口的子类,很可能出现很多方法是空着的情况,因为你的接口设计的过大,导致接口中原本不该出现的方法出现了,结果现在子类根本用不上这个方法,但由于JAVA语言规则的原因,实现一个接口必须实现它的全部方法,所以我们的子类不得不被迫写一堆空方法在那,只为了编译通过。

所以为了解决这一问题,缺省适配器就出现了。比如我们有如下接口。

public interface Person {

    void speak();

    void listen();

    void work();

}

这是一个人的接口,这个接口表示了人可以说话,听和工作,假设是两年前的LZ,还在家待业呢,LZ没工作啊,但是LZ也是个人啊,所以LZ要实现这个接口,所以LZ只能把work方法抄下来空着放在那了,假设LZ是个聋哑人,好吧,三个方法都要空着了,但是LZ表示,LZ是人,LZ一定要实现Person接口。

当然,上述只是举个例子,但是真实项目当中也会出现类似的情况,那么怎么办呢?

这下来了,我们的缺省适配器来了,如下。

public class DefaultPerson implements Person{

    public void speak() {
    }

    public void listen() {
    }

    public void work() {
    }

}

我们创造一个Person接口的默认实现,它里面都是一些默认的方法,当然这里因为没什么可写的就空着了,实际当中可能会加入一些默认情况下的操作,比如如果方法返回结果整数,那么我们在缺省适配器中可以默认返回个0。

这下好了,LZ只要继承这个默认的适配器(DefaultPerson),然后覆盖掉LZ感兴趣的方法就行了,比如speak和listen,至于work,由于适配器帮我们提供了默认的实现,所以就不需要再写了。

这种情况其实蛮多的,因为接口设计的最小化只是理想状态,难免会有一些实现类,对其中某些方法不感兴趣,这时候,如果方法过多,子类也很多,并且子类的大部分方法都是空着的,那么就可以采取这种方式了。

当然,这样做违背了里氏替换原则,但是上面的做法原本就违背了接口的最小化原则,所以我们在真正使用时要权衡二者的利弊,到底我们需要的是什么。所以从此也可以看出来,原则只是指导,并不一定也不可能全部满足,所以我们一定要学会取舍。

总结下两种实现方式的适配器所使用的场景,两者都是为了将已有类的代码复用并且适配到客户端需要的接口上去。

 1,第一种类适配器,一般是针对适配目标是接口的情况下使用。

                 2,第二种对象适配器,一般是针对适配目标是类或者是需要复用的对象多于一个的时候使用,这里再专门提示一下,对象适配器有时候是为了将多个类一起适配,所以才不得不使用组合的方式,而且我们采用对象适配器的时候,继承也不是必须的,而是根据实际的类之间的关系来进行处理,上述例子当中一定要直接或间接的继承自BaseEntity是为了不破坏我们原来的继承体系,但有些情况下这并不是必须的。

对于第三个缺省适配器,一般是为了弥补接口过大所犯下的过错,但是也请注意衡量利弊,权衡好以后再考虑是否要使用缺省适配器。

好了,本次适配器模式的分享就到此结束了,希望各位可以从中得到点收获。

最后,感谢您的收看。

时间: 2024-08-11 02:43:03

(八)适配器模式的相关文章

Java设计模式菜鸟系列(八)适配器模式建模与实现

转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/39805069 适配器模式(Adapter):将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题. 主要分为三类:类的适配器模式.对象的适配器模式.接口的适配器模式. 一.类的适配器模式 1.uml建模: 2.代码实现 /** * 示例(一):类的适配器模式 * * 原类拥有一个待适配的方法originMethod */ class Origin

设计模式八 适配器模式

0.基本定义 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica } 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 通俗的讲,在不改变老系统的功能接口情况下,作向下兼容. spring中以Adapter结尾的都是. 1.实例代码(类间继承) 功能:对原来对账号密码登入方式,添加第三方登入. 老接口: public class SiginService { pu

Java设计模式菜鸟系列总结及博客全目录

转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/40031567 今天来对这23种设计模式做个总结.咱使用设计模式的目的是为了可重用代码.让代码更容易被他人理解.保证代码可靠性,当然设计模式并不是万能的,项目中的实际问题还有具体分析.咱不能为了使用设计模式而使用,而是在分析问题的过程中,想到使用某种设计模式能达到咱需要的效果,而且比不使用设计模式更有优势,那么咱该考虑使用设计模式了. 一.设计模式的一般分类 创建型(Creator)模式(

Java设计模式博客全文件夹

转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/40031567 今天来对这23种设计模式做个总结.咱使用设计模式的目的是为了可重用代码.让代码更easy被他人理解.保证代码可靠性.当然设计模式并非万能的.项目中的实际问题还有详细分析. 咱不能为了使用设计模式而使用,而是在分析问题的过程中.想到使用某种设计模式能达到咱须要的效果,并且比不使用设计模式更有优势.那么咱该考虑使用设计模式了. 一.设计模式的一般分类 创建型(Creator)模

设计模式(八):适配器模式

一.概述 适配器模式将一个类的接口,转换为客户期望的另一个接口.适配器让原本不兼容的类可以合作无间 二.解决问题 从模式的定义中,我们看到适配器模式就是用来转换接口,解决不兼容问题的.想想我们现实生活中的适配器,最常用的就是手机充电器了,也叫做电源适配器,它把家用交流强电转换为手机用的直流弱电.其中交流电就是被适配者,充电器是适配器,手机是用电客户. 三.结构类图 四.成员角色 客户(Client):只能调用目标接口功能,不能直接使用被适配器,但可以通过适配器的接口转换间接使用被适配器. 目标接

Java设计模式(八) 适配器模式

原创文章,转载请务必将下面这段话置于文章开头处. 本文转发自Jason's Blog,原文链接 http://www.jasongj.com/design_pattern/adapter/ 适配器模式介绍 适配器模式定义 适配器模式(Adapter Pattern),将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 适配器模式类图 适配器模式类图如下 适配器模式角色划分 目标接口,如上图中的ITarget 具体目标实现,如Concr

设计模式(八)---适配器模式

1.简介 将一个类的接口转换成客户希望的另外一个接口.使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 2.适配器模式的两种形式及实现方式 2.1.类的适配器模式 (采用继承实现) 2.2.对象的适配器模式 (采用委派方式 即对象组合方式实现) 3.类的适配器模式 类的适配器模式把适配的类的API转换成为目标类的API.类图如下 在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法.为使客户端能够使用Adaptee类,提供一个中间环

八、适配器模式

适配器模式,用一个类作为中间桥梁把无法被直接使用的功能类通过适配,最终能够被间接使用. 如图: 优点:适配器模式能够提高代码的复用性,使得原本无法被使用的类能够被使用. 缺点:但是适配器模式会提高代码的复杂性,让原本简单的逻辑结构变得有些绕,如果使用了大量的适配器,那整个程序结构就会变得混乱不堪,所以如果能够通过重构完成的,还是不要使用适配器比较好. 下面是代码: /** * 适配器模式 * @author lay */ public class AdapterDemo { public sta

(八)适配器模式

1. 定义 适配器模式(Adapter),将一个类的接口转换成客户希望的另一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.[DP] 适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况. 适配器模式主要分为两种: 类适配器模式 对象适配器模式 2. 何时使用适配器模式 使用一个已经存在的类,但如果它的接口和需要的不相同时,就应该考虑用适配器模式. 首先不应该考虑用适配器,而是应该考虑通过重构统一接口,只有双方都不太容易修改的时候再使