适配器模式

现实生活中的适配器


本文讨论适配器模式。适配器模式是23中设计模式之一,它的主要作用是在新接口和老接口之间进行适配。它非常像我们出国旅行时带的电源转换器。为了举这个例子,我还特意去京东上搜了一下电源转换器,确实看到了很多地方的标准不一样。我们国家的电器使用普通的扁平两项或三项插头,而去外国的话,使用的标准就不一样了,比如德国,使用的是德国标准,是两项圆头的插头。如果去德国旅游,那么我们使用的手机充电器插头无法插到德国的插排中去,那就意味着我们无法给手机充电。怎样解决这个问题呢?只要使用一个电源转化器就行了。如下图所示: \

该适配器下面的插头符合德国标准,可以插到德国的插排中去,上面提供的接口符合国标,可以供我们的手机充电器使用。 


实现电源适配器


下面我们使用代码来表述适配器模式: 
代码中有两个接口,分别为德标接口和国标接口,分别命名为DBSocketInterface和GBSocketInterface,此外还有两个实现类,分别为德国插座和中国插座,分别为DBSocket和GBSocket。为了提供两套接口之间的适配,我们提供了一个适配器,叫做SocketAdapter。除此之外,还有一个客户端,比如是我们去德国旅游时住的一家宾馆,叫Hotel,在这个德国旅馆中使用德国接口。 
德标接口: 

/**
 * 德标接口
 */
public interface DBSocketInterface {
     
    /**
     * 这个方法的名字叫做:使用两项圆头的插口供电
     * 本人英语就这个水平
     */
    void powerWithTwoRound();
}

 


德国插座实现德标接口 

/**
 * 德国插座
 */
public class DBSocket implements DBSocketInterface{
     
    public void powerWithTwoRound(){
        System.out.println("使用两项圆头的插孔供电");
    }
}

 

 

德国旅馆是一个客户端,它里面有德标的接口,可以使用这个德标接口给手机充电: 

/**
 * 德国宾馆
 */
public class Hotel {
 
    //旅馆中有一个德标的插口
    private DBSocketInterface dbSocket;
     
    public Hotel(){}
     
    public Hotel(DBSocketInterface dbSocket) {
        this.dbSocket = dbSocket;
    }
 
    public void setSocket (DBSocketInterface dbSocket){
        this.dbSocket = dbSocket;
    }
 
    //旅馆中有一个充电的功能
    public void charge(){
         
        //使用德标插口充电
        dbSocket.powerWithTwoRound();
    }
}

 

现在写一段代码进行测试: 

public class Test {
 
    public static void main(String[] args) {
         
        //初始化一个德国插座对象, 用一个德标接口引用它
        DBSocketInterface dbSoket = new DBSocket();
         
        //创建一个旅馆对象
        Hotel hotel = new Hotel(dbSoket);
         
        //在旅馆中给手机充电
        hotel.charge();
    }
}

 


运行程序,打印出以下结果: 使用两项圆头的插孔供电 
现在我去德国旅游,带去的三项扁头的手机充电器。如果没有带电源适配器,我是不能充电的,因为不可能为了我一个旅客而为我更改墙上的插座,更不可能为我专门盖一座使用中国国标插座的宾馆。因为人家德国人一直这么使用,并且用的挺好,俗话说入乡随俗,我就要自己想办法来解决问题。对应到我们的代码中,也就是说,上面的Hotel类,DBSocket类,DBSocketInterface接口都是不可变的(由德国的客户提供),如果我想使用这一套API,那么只能自己写代码解决。 
下面是国标接口和中国插座的代码。 
国标接口: 

/**
 * 国标接口
 */
public interface GBSocketInterface {
     
    /**
     * 这个方法的名字叫做:使用三项扁头的插口供电
     * 本人英语就这个水平,从有道词典查得, flat意思好像是: 扁的
     */
    void powerWithThreeFlat();
}

 


中国插座实现国标接口: 

/**
 * 中国插座
 */
public class GBSocket implements GBSocketInterface{
     
    @Override
    public void powerWithThreeFlat() {
        System.out.println("使用三项扁头插孔供电");
    }
}

 


可以认为这两个东西是我带到德国去的,目前他们还不能使用,因为接口不一样。那么我必须创建一个适配器,这个适配器必须满足以下条件: 
1 必须符合德国标准的接口,否则的话还是没办法插到德国插座中; 2 在调用上面实现的德标接口进行充电时,提供一种机制,将这个调用转到对国标接口的调用 。 
这就要求: 1 适配器必须实现原有的旧的接口 2 适配器对象中持有对新接口的引用,当调用旧接口时,将这个调用委托给实现新接口的对象来处理,也就是在适配器对象中组合一个新接口。 

下面给出适配器类的实现: 

public class SocketAdapter 
        implements DBSocketInterface{   //实现旧接口
 
    //组合新接口
    private GBSocketInterface gbSocket;
     
    /**
     * 在创建适配器对象时,必须传入一个新街口的实现类
     * @param gbSocket
     */
    public SocketAdapter(GBSocketInterface gbSocket) {
        this.gbSocket = gbSocket;
    }
 
     
    /**
     * 将对就接口的调用适配到新接口
     */
    @Override
    public void powerWithTwoRound() {
         
        gbSocket.powerWithThreeFlat();
    }
 
}

 


这个适配器类满足了上面的两个要求。下面写一段测试代码来验证一下适配器能不能工作,我们按步骤一步步的写出代码,以清楚的说明适配器是如何使用的。 
1 我去德国旅游,带去的充电器是国标的(可以将这里的GBSocket看成是充电器) 

    
GBSocketInterface gbSocket = new GBSocket();

 

 

2 来到德国后, 找到一家德国宾馆住下 (这个宾馆还是上面代码中的宾馆,使用的依然是德国标准的插口) 

Hotel hotel = new Hotel();

 


3 由于没法充电,我拿出随身带去的适配器,并且将我带来的充电器插在适配器的上端插孔中。这个上端插孔是符合国标的,我的充电器完全可以插进去。 

SocketAdapter socketAdapter = new SocketAdapter(gbSocket);

4 再将适配器的下端插入宾馆里的插座上 

hotel.setSocket(socketAdapter);

5 可以在宾馆中使用适配器进行充电了 

hotel.charge();

上面的五个步骤就是适配器的使用过程,下面是完整的测试代码。 

public class TestAdapter {
 
    public static void main(String[] args) {
         
        GBSocketInterface gbSocket = new GBSocket();
         
        Hotel hotel = new Hotel();
         
        SocketAdapter socketAdapter = new SocketAdapter(gbSocket);
         
        hotel.setSocket(socketAdapter);
         
        hotel.charge();
    }
}

 


运行上面的程序,打印出以下结果: 使用三项扁头插孔供电

这说明适配器起作用了,上一个实例中打印的是:使用两项圆头的插孔供电。 现在可以使用三项扁头插孔供电了。我们并没有改变宾馆中的德标插口,提供了一个适配器就能使用国标的插口充电。这就是适配器模式的魅力:不改变原有接口,却还能使用新接口的功能。 
由于上面的代码都是分片的,没有完整的项目源码,为了使读者对示例中的类和接口更清晰,下面给出UML类图: 
\


总结


根据上面的示例,想必读者应该能比较深入的了解到了适配器模式的魔力。下面给出适配器模式的定义(该定义来自于《Head First 设计模式》): 
适配器模式将一个类的接口转换成客户期望的另一个接口,让原本不兼容的接口可以合作无间。 
下面给出适配器模式的类图(该类图同样来自于《Head First 设计模式》): 



适配器模式的三个特点: 
1 适配器对象实现原有接口 2 适配器对象组合一个实现新接口的对象(这个对象也可以不实现一个接口,只是一个单纯的对象) 3 对适配器原有接口方法的调用被委托给新接口的实例的特定方法 

有人认为讲解设计模式的例子都太简单,看着感觉是那么回事,但是要是真想在项目开发中使用,还真是应用不到。其实我们不必在项目中刻意使用设计模式,而是应该从实际的设计问题出发,看哪个模式能解决我们的问题,就使用哪个模式。不要为了使用模式而使用模式,那样就舍本逐末了,一般情况下,只要遵循一定的设计原则就可以了,设计模式也是根据这些原则被总结出来的,熟悉了这些原则,模式自然而然就有了。 
其实只要平时善于思考了感悟,在项目中是可以用到设计模式的,并且如果用的合理的话,会为此而受益良多。

文章转载:http://www.2cto.com/kf/201401/275535.html 

 

 

2015-05-18添加,在网上看到好文章,拷贝为以后参考-------------------------------------------------------------------------------------------------------------------------- 

1. 概述

  将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

2. 解决的问题

  即Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

3. 模式中的角色

  3.1 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。

  3.2 需要适配的类(Adaptee):需要适配的类或适配者类。

  3.3 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。  

4. 模式解读

  注:在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式对象适配器模式。由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、java等语言都不支持多重继承,因而这里只是介绍对象适配器。

  4.1 适配器模式的类图

  

  4.2 适配器模式的代码实现

复制代码
    /// <summary>
    /// 定义客户端期待的接口
    /// </summary>
    public class Target
    {
        /// <summary>
        /// 使用virtual修饰以便子类可以重写
        /// </summary>
        public virtual void Request()
        {
            Console.WriteLine("This is a common request");
        }
    }

    /// <summary>
    /// 定义需要适配的类
    /// </summary>
    public class Adaptee
    {
        public void SpecificRequest()
        {
            Console.WriteLine("This is a special request.");
        }
    }

    /// <summary>
    /// 定义适配器
    /// </summary>
    public class Adapter:Target
    {
        // 建立一个私有的Adeptee对象
        private Adaptee adaptee = new Adaptee();

        /// <summary>
        /// 通过重写,表面上调用Request()方法,变成了实际调用SpecificRequest()
        /// </summary>
        public override void Request()
        {
            adaptee.SpecificRequest();
        }
    }
复制代码

  4.3 客户端代码

复制代码
    class Program
    {
        static void Main(string[] args)
        {
            // 对客户端来说,调用的就是Target的Request()
            Target target = new Adapter();
            target.Request();

            Console.Read();
        }
    }
复制代码

  运行结果

  

5. 模式总结

  5.1 优点

    5.1.1 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。

    5.1.2 复用了现存的类,解决了现存类和复用环境要求不一致的问题。

    5.1.3 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。

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

  5.2 缺点

    对于对象适配器来说,更换适配器的实现过程比较复杂。

  5.3 适用场景

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

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

    5.3.3 两个类所做的事情相同或相似,但是具有不同接口的时候。

    5.3.4 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。

    5.3.5 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

6. 适配器应用举例

  6.1 使用过ADO.NET的开发人员应该都用过DataAdapter,它就是用作DataSet和数据源之间的适配器。DataAdapter通过映射Fill和Update来提供这一适配器。

  6.2 手机电源适配器

文章转自:http://www.cnblogs.com/wangjq/archive/2012/07/09/2582485.html

 

posted on 2014-09-25 09:24  忙碌ing  阅读(217)  评论(0)    收藏  举报

导航