设计模式02——Adapter模式

2020-04-03 18:06:19 浏览数 (1)

定义

适配器设计模式,顾名思义就是将适配器的作用总结抽象成为一种代码的组织方式,将现有的代码通过适配器进行适配,以满足项目对另外一个类或者接口的要求。换句话说就是将一个类的接口适配(包装/转换)成客户(调用者)希望的另一个接口。适配器设计模式有以下两种形式:

  • 类适配器模式(使用继承的适配器)
  • 对象适配器模式(使用委托的适配器)
问题引入

我们常用的笔记本电脑的配件中就有一个适配器,负责将220V交流电转换成为12V的直流电给笔记本电脑供电,它存在的作用就是将220V交流电转换成为12V的直流电。所以它就是适配器,220V交流电就是被适配的对象,而12V直流电就是转换后的目标对象,笔记本电脑就是这个目标对象的调用者。

适配器设计模式在JDK源码中的应用

学习适配器设计模式,当然也需要从JDK中去寻找它的踪迹,在JDK源码中,采用适配器设计模式的地方很多,比如最常见的IO转换流和集合等。接下来我们一起从源码中来分析适配器设计模式是如何使用起来的。 我们一起阅读一下java.io.InputStreamReader(InputStream)的部分源码:

InputStreamReader的作用是将字节流转换为字符流,是它们之间转换的桥梁(适配器),也就是说,InputStreamReader就是适配器,负责将InputStream转换为Reader,这样就可以使用Reader的方法来执行各项操作。

代码语言:javascript复制
package java.io;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import sun.nio.cs.StreamDecoder;

public class InputStreamReader extends Reader {

    private final StreamDecoder sd;
    
    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }
    
    public String getEncoding() {
        return sd.getEncoding();
    }

    public int read() throws IOException {
        return sd.read();
    }

    public int read(char cbuf[], int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length);
    }

    public boolean ready() throws IOException {
        return sd.ready();
    }

    public void close() throws IOException {
        sd.close();
    }
}

上面的代码是经过删减后的部分代码,删除了源码中的注释以及几个构造方法,从阅读源码来看,这个适配器设计模式的形式采用的是“对象适配器模式”,至于何是“对象适配器模式”,我们将在后面的学习中介绍,读者可以暂时不必理会何是“对象适配器模式”。

手动实现适配器设计模式

接下来,我们将手动实现两种适配器设计模式,用简单的代码来说明适配器设计模式是如何运转起来的。

以前小时候都玩过QQ游戏,有时候需要充值Q币,使用Q币在游戏中购买道具,这种情景就可以完全适用适配器设计模式,那么在这种情景中,使用Q币来购买游戏道具是我们需求,也就是我们的目标(Target),而现在的现状是我们有人民币,那么人民币就是被适配的对象(Adaptee),由于人民币不能直接在游戏中购买道具,它需要被转换成Q币才可以进行交易,所以我们还需要一个适配器(Adapter),负责将人民币转换成Q币。 根据以上的文字,我们可以将其总结成为一个表格,可以很方便地理清关系:

角色

扮演者

作用

Target

购买游戏道具的接口

有充值Q币接口,有使用Q币充值游戏道具的接口

Adapter

Q币充值器

将人民币转换成为Q币,并完成充值

Adaptee

人民币

被适配,被转换的对象

根据以上的关系,我们分别来创建各个角色对应的类或者接口。这里我们模拟了使用人民币充值Q币,使用Q币购买游戏道具的案例,假设一个单位的人民币可以充值10Q币,每个Q币可以一个游戏道具。

示例代码1:类适配器设计模式(使用继承的适配器)
  • Target

我们的目标是有一个接口,这个接口可以购买游戏道具,但是需要使用Q币来进行购买。在适配器设计模式里,它是我们需要最终的目标。

代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example3;

/**
 * @author jiangpingping
 * @date 2018/9/6 下午7:43
 */
public interface TargetInterface {

    /**
     * 购买qCoinCount个游戏道具
     */
    void buyGameProps();

}
  • Adaptee

现在的现状是手头上有人民币,所以需要有一个Q币充值器,将人民币换成相同价值的Q币。在适配器设计模式里面,人民币就是需要被适配的对象,因为它不能直接用来购买游戏道具,但是得必须通过它才可以充值Q币,才能满足要求。

代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example3;

/**
 * 人民币
 *
 * @author jiangpingping
 * @date 2018/9/6 下午7:45
 */
public class Rmb {

    private int count;

    public Rmb(int count) {
        this.count = count;
    }

    public int getCount() {
        return this.count;
    }

}
  • Adapter

所以我们需要一个Q币充值器,将人民币换成Q币,然后还具备购买游戏道具的功能。在适配器设计模式中,Q币充值器就是我们所需的适配器。

代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example3;

/**
 * Q币充值器
 *
 * @author jiangpingping
 * @date 2018/9/6 下午7:31
 */
public class QCoinRechargeableDevice extends Rmb implements TargetInterface {

    private int qCoinCount;

    public QCoinRechargeableDevice(int rmbCount) {
        super(rmbCount);
        this.qCoinCount = getCount() * 10;
    }

    @Override
    public void buyGameProps() {
        System.out.println("一共购买了"   qCoinCount   "个道具");
    }
}
  • Main

这里写一个Main方法,来验证我们上面设计的适配器设计模式,主要代码如下:

代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example3;

/**
 * @author jiangpingping
 * @date 2018/9/6 下午9:26
 */
public class Main {

    public static void main(String[] args) {
        TargetInterface qCoinRechargeableDevice = new QCoinRechargeableDevice(10);
        qCoinRechargeableDevice.buyGameProps();
    }
}

这里,我们向Q币充值器充值10个单位的人民币,就可以完成购买100个游戏道具转变。本来人民币不能直接用来购买游戏道具,使用适配器设计模式之后,就可以完成我们购买游戏道具的需求。

使用继承的适配器UML类图

使用继承的适配器有一个特点就是Adapter继承了Adaptee,并实现了Target,这就是三者之间的关系。

示例代码2:对象适配器设计模式(使用委托的适配器)

这里仅仅是贴出代码,对于各个类的说明,在上面都已经进行了阐述。

  • Target
代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example4;

/**
 * @author jiangpingping
 * @date 2018/9/10 下午9:16
 */
public abstract class TargetInterface {

    /**
     * 购买qCoinCount个游戏道具
     */
    public abstract void buyGameProps();
}
  • Adaptee
代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example4;

/**
 * 人民币
 *
 * @author jiangpingping
 * @date 2018/9/6 下午7:45
 */
public class Rmb {

    private int count;

    public Rmb(int count) {
        this.count = count;
    }

    public int getCount() {
        return this.count;
    }

}
  • Adapter
代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example4;

/**
 * @author jiangpingping
 * @date 2018/9/10 下午9:18
 */
public class QCoinRechargeableDevice extends TargetInterface {

    private Rmb rmb;

    public QCoinRechargeableDevice(Rmb rmb) {
        this.rmb = rmb;
    }

    @Override
    public void buyGameProps() {
        System.out.println("一共购买了"   rmb.getCount() * 10   "个道具");
    }
}
  • Main
代码语言:javascript复制
package cn.itlemon.design.pattern.chapter02.adapter.example4;

/**
 * @author jiangpingping
 * @date 2018/9/10 下午9:16
 */
public class Main {

    public static void main(String[] args) {
        TargetInterface qCoinRechargeableDevice = new QCoinRechargeableDevice(new Rmb(10));
        qCoinRechargeableDevice.buyGameProps();
    }
}
使用委托(对象)的适配器UML类图

使用委托的适配器有一个特点就是Adapter拥有了Adaptee,并继承了Target抽象类,这就是三者之间的关系。

浅析适配器模式中的重要角色

适配器设计模式也是一个比较常用的设计模式之一,现对适配器设计模式中的角色进行浅析。

  • Target(对象) 该角色负责定义最终的需求,也就是使用适配器模式之后的最终效果。在本次示例中,TargetInterface就是扮演了这个Target角色。
  • Adaptee(被适配) 该角色定义的是原始的功能,它也许无法直接被利用,但是又不能随意更改,所以它就需要被适配,使得在不修改原始代码的情况下能激活Target的功能。在本次示例中,Rmb扮演了这个角色。
  • Adapter(适配) 该角色是适配器设计模式的核心角色,他负责适配AdapteeTarget,使得Adaptee来满足Target的需求。在本次示例中,QCoinRechargeableDevice扮演了这个角色。
  • Client(请求者) 该角色负责调用Target的方法来进行一系列的逻辑处理。在本次示例中,Main类扮演了这个角色。
适配器设计模式UML类图

分析完适配器设计模式的重要角色,当然也得理清适配器设计模式的UML类图。

  • 使用继承的适配器设计模式类图
  • 使用委托的适配器设计模式类图
为什么要使用适配器设计模式

我们往往有这种思想,要使用什么类的方法,直接使用不就OK了,或者稍微修改一下已有的代码不就可以使用了吗?其实这种思想是不正确的,因为在现有类的基础下,很多类的方法都经过了严格的测试,贸然地去修改他容易造成意外情况的发生,我们使用适配器设计模式,往往无需修改现有的代码,直接在现有的代码的基础上创建新的代码,这样即使出了错误,我们也能很快从我们新写的代码中找出端倪。使用适配器设计模式,也是对现有代码的一种重复利用。

0 人点赞