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

1.适配器模式的概念

  适配器模式,将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  适配器模式主要用于解决的问题是需要的东西就在面前,但却不能使用,而短时间无法改造它,于是就想办法去适配的,具体的在使用场景例子在后面给出。

2.适配器模式的分类及其结构图

适配器模式其实也分为两种,一种是对象适配器,而另一种是类适配器。我们先来说说对象适配器吧。

(1)对象适配器

  对象适配器,顾名思义,在这个适配器中包含了一个对象,也就是说对象适配器中保留了一个原对象的引用,这个原对象就是Adaptee,这种适配器的工作原理就是将这个适配器继承Target,然后复写Target中的方法。具体复现Target方法的过程,就是使用Adaptee中相应的方法去实现要复写的方法。我们可以看看对象适配器的UML图:
这里写图片描述
Target类:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
Adapter类:通过在内部包装一个Adaptee对象,把源接口转换成目标接口。
Adaptee类:需要适配的类。

(2)类适配器

  类适配器的实现就不像对象适配器那样是通过保留对象的引用来实现了,类适配器的实现是通过类的继承来实现的。Adapter类通过直接继承Target类和Adaptee类中的所有方法,然后根据要适配的方法来进行改写即可。但是我们都知道Java不支持多继承,所以通常我们将Target类设计为接口,让Adapter继承Adaptee类并实现Target接口,这样就可以作为类适配器进行使用。
我们来看看类适配器的UML图:
这里写图片描述

3.两种适配器模式的基本实现

(1)对象适配器的代码实现

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

package com.jxs.adapter;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
 */
public class Client {

    public static void main(String[] args) {

        Target target = new Adapter();
        target.request();
    }
}

Adaptee类(需要适配的类):

package com.jxs.adapter;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 需要适配的类
 */
public class Adaptee {

    public void specificRequest() {

        System.out.println("特殊请求");
    }
}

Adapter(通过在内部包装一个Adaptee对象,把源接口转换成目标接口):

package com.jxs.adapter;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 通过在内部包装一个Adaptee对象,把源接口转换成目标接口
 */
public class Adapter extends Target {

    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {

        adaptee.specificRequest();
    }
}

客户端代码如下:

package com.jxs.adapter;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
 */
public class Client {

    public static void main(String[] args) {

        Target target = new Adapter();
        target.request();
    }
}

运行结果:

特殊请求

Process finished with exit code 0
(2)类适配器的代码实现

Target:

package com.jxs.adapterClass;

/**
 * Created by jiangxs on 2018/5/9.
 */
public interface Target {

    void request();
}

Adapter:

package com.jxs.adapterClass;

import com.jxs.adapterObject.Adapter;
import com.jxs.adapterObject.Target;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
 */
public class Client {

    public static void main(String[] args) {

        Target target = new Adapter();
        target.request();
    }
}

Adaptee:

package com.jxs.adapterClass;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 需要适配的类
 */
public class Adaptee {

    public void specificRequest() {

        System.out.println("特殊请求");
    }
}

客户端:

package com.jxs.adapterClass;

import com.jxs.adapterObject.Adapter;
import com.jxs.adapterObject.Target;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
 */
public class Client {

    public static void main(String[] args) {

        Target target = new Adapter();
        target.request();
    }
}

运行结果:

特殊请求

Process finished with exit code 0

4.适配器模式的例子

  例如姚明当初去NBA打球,但是他并听不能听懂英语,篮球教练叽里呱啦讲了半天他也并不知道他讲了些什么东西,这时候姚明和教练之间需要一个翻译,这个翻译将教练说的这些战术性的内容翻译给姚明听,这样姚明就知道战术的内容是什么了。这个翻译就充当了适配器的角色。假设这个篮球队中包含有前锋,中锋,后卫,前锋和后卫都是会说英语的两个球员,而中锋是姚明(只有姚明听不太懂英语),还有一个翻译(给姚明当翻译),他们之间的结构关系图大致如下:
这里写图片描述
上面的代码用对象适配器的方式实现如下:
Player(球员类,相当于Target类):

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 * <p>
 * 球员类,抽取球员的都有的方法
 */
public abstract class Player {

    private String name;

    public Player(String name) {

        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract void attack();

    public abstract void defense();
}

Forwards类(前锋类,实现Target接口,可以直接使用而无需适配器的类):

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class Forwards extends Player {

    private String name;

    public Forwards(String name) {

        super(name);
        this.name = name;
    }

    @Override
    public void attack() {

        System.out.println("前锋 " + name + " 进攻!");
    }

    @Override
    public void defense() {

        System.out.println("前锋 " + name + " 进攻!");
    }
}

Guards类(后卫类,实现Target接口,可以直接使用而无需适配器的类):

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class Guards extends Player {

    private String name;
    public Guards(String name) {

        super(name);
        this.name = name;
    }

    @Override
    public void attack() {

        System.out.println("后卫 " + name + " 进攻!");
    }

    @Override
    public void defense() {

        System.out.println("后卫 " + name + " 防守");
    }
}

Translator类(翻译类,用做适配器,相当于Adapter类):

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class Translator extends Player {

    private ForeignCenter foreignCenter = new ForeignCenter();

    public Translator(String name) {

        super(name);
        foreignCenter.setName(name);
    }

    @Override
    public void attack() {

        foreignCenter.jinGong();
    }

    @Override
    public void defense() {

        foreignCenter.fangShou();
    }
}

ForeignCenter类(相当于Adaptee类,用做被适配的对象):

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class ForeignCenter {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void jinGong() {

        System.out.println("外国中锋 " + name + " 进攻!");
    }


    public void fangShou() {

        System.out.println("外国中锋 " + name + " 防守");
    }
}

客户端:

package com.jxs.adapterExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class Client {

    public static void main(String[] args) {

        Player b = new Forwards("巴蒂尔");
        b.attack();

        Player m = new Guards("麦克格雷迪");
        m.attack();

        Player ym = new Translator("姚明");
        ym.attack();
        ym.defense();

    }
}

运行结果:

前锋 巴蒂尔 进攻!
后卫 麦克格雷迪 进攻!
外国中锋 姚明 进攻!
外国中锋 姚明 防守

Process finished with exit code 0

  因为上面已经举过类适配器实现的基本代码,用类适配的方式实现这个例子与其基本方式的实现大同小异,这里不再赘述,要想查阅这部分的代码,可以去我的github上下载相关源码进行比较和查看。(github地址在文章末尾给出)

5.缺省适配器

  假设上面的举得例子中说的那个篮球队的所有运动员都喜欢抽烟喝酒烫头,但是只有姚明一个人不抽烟,不烫头,他喜欢喝酒打麻将,这个怎么用适配器模式来实现呢?我们都知道,姚明和那些抽烟喝酒烫头的队员都是火箭队的队员,那就让火箭队去实现这些抽烟喝酒烫头的队员的所有嗜好的方法,然后让姚明去继承火箭队,这样姚明就可以不用实现火箭队的所有方法,可以实现里面的一部分方法,并增加其新的方法。这个火箭队其实就是一个缺省适配器。下面我们来看看代码是如何实现的:
Player类:

package com.jxs.adapterDefaultExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public interface Player {

    void smoke();

    void drink();

    void perm();
}

Rocket类(DefaultAdapter):

package com.jxs.adapterDefaultExample;

/**
 * Created by jiangxs on 2018/5/9.
 *
 * Rocket这个抽象类就相当于DefaultAdapter(缺省适配器)
 */
public abstract class Rocket implements Player{

    @Override
    public void smoke() {}

    @Override
    public void drink() {}

    @Override
    public void perm() {}
}

YaoMing类:

package com.jxs.adapterDefaultExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class YaoMing extends Rocket {

    @Override
    public void drink() {

        System.out.println("活冰啤酒");
    }

    public void PlayMaJiang() {

        System.out.println("打血流成河");
    }
}

客户端:

package com.jxs.adapterDefaultExample;

/**
 * Created by jiangxs on 2018/5/9.
 */
public class Client {

    public static void main(String[] args) {

        YaoMing ym = new YaoMing();
        ym.drink();
        ym.PlayMaJiang();
    }
}

运行结果:

活冰啤酒
打血流成河

Process finished with exit code 0

6.适配器模式的总结

(1)适配器模式的优点

①将目标类和适配者类解耦。
②增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
③灵活性和扩展性都非常好,符合开闭原则。
④类适配器的优点:由于适配器类是适配者类的子类,因此可以再适配器类中置换一些适配者的方法,使得适配器的灵活性更强
⑤对象适配器的优点:把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。

(2)适配器模式的缺点

①类适配器的缺点:对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。
②对象适配器的缺点:与类适配器模式相比,要想置换适配者类的方法就不容易。

(3)适配器模式的适用场景

①系统需要使用现有的类,而这些类的接口不符合系统的接口。
②想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
③两个类所做的事情相同或相似,但是具有不同接口的时候。
④旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
⑤使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

注:以上代码均可在github上进行下载:https://github.com/xsongj/designPattern

参考:《大话设计模式》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值