设计模式(十)适配器模式

一、问题引入

说起适配器其实在我们的生活中是非常常见的,比如:如果你到日本出差,你会发现日本的插座电压都是110V的,而我们的手机充电器和笔记本充电器都是220V,所以你到了日本之后就没办法充电了,这时候我们通常会怎么办呢,当然是使用一个升压的变压器将电压升高到220V,这样我们的手机通过一个变压器(适配器)就能使用原本不能使用的插座了。

又比如说,有的国家的插座都是三孔的,而我们的手机大部分都是两孔的,这是你也没办法直接把充电器插到插座上,这时我们可以使用一个适配器,适配器本身是三孔的,它可以直接插到三孔的插头上,适配器本身可以提供一个两孔的插座,然后我们的手机充电器就可以插到适配器上了,这样我们原本只能插到两孔上的插头就能用三孔的插座了。

在我们的面向对象里也存在这个问题,假设一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口,不同于旧厂商的接口,就像下图这样:

你不想改变现有的代码,解决这个问题(而且你也不能改变厂商的代码)。所以该怎么做?这个嘛,你可以写一个类,将新厂商的接口转化成你所希望的接口。

这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类能理解的请求。

这样的话,就能在不改变现有代码的情况下使用原本不匹配的类库了。

二、适配器模式的相关概念

经过上边的三个例子,我们可以总结出适配器模式的使用过程:

1、客户通过目标接口调用适配器的方法对适配器发出请求。

2、适配器使用被适配者接口把请求转化成被适配者的一个或多个调用接口。

3、客户接收到调用的结果,但并未察觉这一切是适配在起转化作用。

所以适配器模式的正式定义就是:

适配器模式将一个类的接口,转化成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

三、对象适配器

适配器其实是分为对象适配器和类适配器两种,两种的工作原理不太一样。对象适配器是使用组合的方法,在Adapter中会保留一个原对象(Adaptee)的引用,适配器的实现就是讲Target中的方法委派给Adaptee对象来做,用Adaptee中的方法实现Target中的方法。

这种类型的好处就是,Adpater只需要实现Target中的方法就好啦。

现在我们通过一个用火鸡冒充鸭子的例子来看看如何使用适配器模式。

package com.designpattern.adapter.object;

public abstract class Duck {
    /**
     * 嘎嘎叫
     */
    public abstract void quack();

    public abstract void fly();
}
package com.designpattern.adapter.object;

public abstract class Turkey {
    /**
     * 火鸡叫
     */
    public abstract void gobble();

    public abstract void fly();
}
package com.designpattern.adapter.object;

public class WildTurkey extends Turkey {
    public void gobble() {
        System.out.println("Gobble gobble");
    }

    public void fly() {
        System.out.println("I‘m flying a short distance");
    }
}
package com.designpattern.adapter.object;

/**
 * 用火鸡冒充鸭子
 * @author 98583
 *
 */
public class TurkeyAdapter extends Duck {
    /**
     * 保留火鸡的引用
     */
    Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    /**
     * 利用火鸡的叫声来实现鸭子的叫声
     */
    public void quack() {
        turkey.gobble();
    }

    /**
     * 利用火鸡的飞的方法来实现鸭子的飞的方法
     */
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}
package com.designpattern.adapter.object;

/**
 * 用火鸡冒充鸭子
 * @author 98583
 *
 */
public class Client {
    public static void main(String[] args) {

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}

鸭子和火鸡有相似之处,他们都会飞,虽然飞的不远,他们不太一样的地方就是叫声不太一样,现在我们有一个火鸡的类,有鸭子的抽象类也就是接口。我们的适配器继承自鸭子类并且保留了火鸡的引用,重写鸭子的飞和叫的方法,但是是委托给火鸡的方法来实现的。在客户端中,我们给适配器传递一个火鸡的对象,就可以把它当做鸭子来使用了。

四、类适配器

与对象适配器不同的是,类适配器是通过类的继承来实现的。Adpater直接继承了Target和Adaptee中的所有方法,并进行改写,从而实现了Target中的方法。

这种方式的缺点就是必须实现Target和Adaptee中的方法,由于Java不支持多继承,所以通常将Target设计成接口,Adapter继承自Adaptee然后实现Target接口。

我们使用类适配器的方式来实现一下上边的用火鸡来冒充鸭子。

package com.designpattern.adapter.classmethod;

/**
 * 由于Java不支持多继承,所以通常将Target声明为接口
 * @author 98583
 *
 */
public interface Duck {
    /**
     * 嘎嘎叫
     */
    public void quack();

    public void duckFly();
}
package com.designpattern.adapter.classmethod;

/**
 * 目前已有的火鸡类的抽象类
 * @author 98583
 *
 */
public abstract class Turkey {
    /**
     * 火鸡叫
     */
    public abstract void gobble();

    public abstract void turkeyFly();
}
package com.designpattern.adapter.classmethod;

/**
 * 用火鸡冒充鸭子,不再保留火鸡类的引用,需要实现鸭子类和火鸡类的方法
 * @author 98583
 *
 */
public class TurkeyAdapter extends Turkey implements Duck {

    /**
     * 利用火鸡的叫声来实现鸭子的叫声
     */
    public void quack() {
        gobble();
    }

    /**
     * 利用火鸡的飞的方法来实现鸭子的飞的方法
     */
    public void turkeyFly() {
        for (int i = 0; i < 5; i++) {
            System.out.println("I‘m flying a short distance");
        }
    }

    /**
     * 使用火鸡类的方法来实现鸭子类的方法
     */
    public void duckFly() {
        turkeyFly();
    }

    /**
     * 火鸡的叫声
     */
    public void gobble() {
        System.out.println("Gobble gobble");
    }

}
package com.designpattern.adapter.classmethod;

/**
 * 用火鸡冒充鸭子
 * @author 98583
 *
 */
public class Client {
    public static void main(String[] args) {

        Duck turkeyAdapter = new TurkeyAdapter();

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.duckFly();
    }
}

其实两种方法的效果是一样的,只是用的方法不一样。Java不支持多继承,所以将Duck声明为接口,Adapter继承自火鸡类并且实现了Duck的方法,但是实现Duck的方法不再是委派给火鸡类的对象,而是直接调用火鸡类的方法,因为在Adapter中实现了火鸡类的方法,所以可以直接调用。

五、缺省适配器

鲁达剃度的故事就很好的说明了缺省适配器的作用。一般的和尚都是吃斋,念经,打坐,撞钟和习武,但是鲁达只是喝酒喝习武,所以‘鲁达不能剃度(不能当做和尚使用),要想让鲁达可以当做和尚使用就要让他实现和尚的所有方法,但是这样做时候鲁达就不是鲁达了。我们可以找一个中间者,比如鲁达是天星的一位,我们可以让天星实现和尚所有的方法,再让鲁达继承自天星。代码如下:

这是定义的和尚接口,和尚都应该做以下的事。

package com.designpattern.adapter.defaultmethod;

public interface Monk {

    public void chizha();
    public void nianjing();
    public void dazuo();
    public void zhuangzhong();
    public void xiwu();
}

这是天星类,为每个方法提供一个空实现,其他继承自该类的子类可以重写父类的方法。

package com.designpattern.adapter.defaultmethod;

public abstract class Star implements Monk{

    public void chizha(){}
    public void nianjing(){}
    public void dazuo(){}
    public void zhuangzhong(){}
    public void xiwu(){}

}

鲁达继承自天星,并且添加了喝酒的方法。

package com.designpattern.adapter.defaultmethod;

public class Luda extends Star{

    public void xiwu(){
        System.out.println("鲁达习武");
    }

    public void hejiu(){
        System.out.println("鲁达喝酒");
    }
}

我们看到通过天星类(缺省适配器),鲁达不需要再实现自己不需要的方法了。

六、优缺点

优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

类适配器模式还具有如下优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还具有如下优点:

一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点:

类适配器模式的缺点如下:

对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器模式的缺点如下:

与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

七、适用环境

1、系统需要使用现有的类,而这些类的接口不符合系统的需要。

2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

源码下载:http://download.csdn.net/detail/xingjiarong/9322221

时间: 2024-08-28 17:18:21

设计模式(十)适配器模式的相关文章

设计模式之适配器模式

适配器模式(Adapter Pattern)有时候也称包装样式或者包装.将一个类的接口转接成用户所期待的.一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类别自己的接口包裹在一个已存在的类中. Adapter Pattern有两种: 类的Adapter Pattern(继承) 对象的Adapter Pattern(委托) 下面我们用第一种来实现适配器模式: 我们首先来看看类图: 具体实现如下: Banner类: public class Banner { private Stri

设计模式之适配器模式(Adapter Pattern)

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 1. 解决的问题 即Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 2. 模式中的角色 2.1 目标接口(Target):客户所期待的接口.目标可以是具体的或抽象的类,也可以是接口. 2.2 需要适配的类(Adaptee):需要适配的类或适配者类. 2.3 适配器(Adapter):通过包装一个需要适配的对象,把

大话设计模式_适配器模式(Java代码)

适配器模式:将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 简单描述: 大话设计模式中的截图: 代码例子: AbstractPlayer类: 1 package com.longsheng.adapter; 2 3 public abstract class AbstractPlayer { 4 5 public abstract void attack(); 6 7 public abstract void defenc

设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕该功能.如查找.排序等,一种经常使用的方法是硬编码(Hard Coding)在一个类中,如须要提供多种查找算法,能够将这些算法写到一个类中,在该类中提供多个方法,每个方法相应一个详细的查找算法:当然也能够将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件推断语句来进行选择.

螃蟹学PHP设计模式之适配器模式

之前对java的设计模式略有了解,但是螃蟹目前是靠PHP吃饭,所以对于进阶学习php设计模式还是很有必要.螃蟹虽然做开发两年了,但是在老鸟眼中还是菜鸟,为了自己写的代码更容易维护和进行团队协作,所以我要搞定设计模式.希望螃蟹今年能创业成功或找个好工作. 1.适配器模式 螃蟹的理解是你买了港行的iphone手机,发现自家的插座和手机自带的插头不配,怎么办呢,就需要一个转换器(适配器).可能原来系统的类不是你设计的,但是现在你要使用原来的类,发现有问题,但原来的类又不能动,或者不需要动,这样你写一个

Java设计模式之适配器模式(Adapter Pattern)

Adapter Pattern的作用是在不改变功能的前提下转换接口.Adapter分为两类,一类是Object Adapter, 另一类是Class Adapter.由于Class Adapter的实现需要用到多继承,而Java不支持多继承,所以这里只关注Object Adapter. 在JDK1.5之前是没有 java.util.Iterator 接口的,java.util.Enumeration 接口起着 Iterator 的作用.那么如果我们需要维护一些年代比较久远的代码,可能就会面临着没

设计模式 ( 十二 ) 职责链模式(Chain of Responsibility)(对象行为)

 设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决.不能解决就推卸给另外个一个部门(对象).至于究竟谁来解决问题呢?政府部门就是为了能够避免屁民的请求与官员之间耦合在一起,让多个(部门)对象都有可能接收请求,将这些(部门)对象连接成一条链,而且沿着这条链传递请求.直到有(部门)对象处理它为止. 样例1:js的事件浮升机制 样例2: 2.问题 假设有多个对象都有

Head First 设计模式之适配器模式与外观模式

Head First设计模式之适配器模式与外观模式 前言: 之前讲过装饰者模式,将对象包装起来并赋予新的职责,这一章我们也会将对象进行包装,只不过是让它们看起来不像自己而像是别的东西.这样就可以在设计中将类的接口转化为想要的接口,以实现同的接口,此外还将讲述另一个模式,将对象包装起来以简化接口. 1.   适配器简介 1.1 面向对象的适配器 真实世界的适配器比如位于美式插头和欧式插座之间的交流电适配器.面向对象的适配器是什么呢? 面向对象的适配器是将一个接口转化成另一个接口,以符合客户的期望.

每天一个设计模式-3 适配器模式(Adapteer)

每天一个设计模式-3 适配器模式(Adapteer) 1.现实中的情况 旧式电脑的硬盘是串口的,直接与硬盘连接,新硬盘是并口的,显然新硬盘不能直接连在电脑上,于是就有了转接线.好了,今天的学习主题出来了“适配器”. 2.联系编程 有一个电源类,一个旧的硬盘类,还有一个新硬盘类:电源类能直接适配旧硬盘类,但不能和新硬盘适配. 这时,可以添加一个适配器类,这里采用对象引用的方式实现适配器. 3.类图 模式简图: 测试时使用的类图: 4.代码 NewDiskApi: public interface

Java编程思想笔记02:组合与继承、final、策略设计模式与适配器模式、内部类、序列化控制(注意事项)

1.组合和继承之间的选择 组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式的做. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形.即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口.为取得此效果,需要在新类中嵌入一个现有类的private对象.但有时,允许类的用户直接访问新类中的组合成分是极具意义的,即将成员对象声明为public.如果成员对象自身都隐藏了具体实现,那么这种做法是安全的.当用户能够了