人人都会设计模式---适配器模式--Adapter

2,110 阅读10分钟

适配器模式大纲
适配器模式大纲

版权声明:本文为博主原创文章,未经博主允许不得转载

PS:转载请注明出处
作者: TigerChain
地址: www.jianshu.com/p/1edf5d944…
本文出自 TigerChain 简书 人人都会设计模式

教程简介

  • 1、阅读对象
    本篇教程适合新手阅读,老手直接略过
  • 2、教程难度
    初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢

正文

一、什么是适配器模式

1、生活中的适配器

比如电脑转接器「这里主要是指连接电脑和投影仪的」,以我的 MAC 电脑为例子,我们公司的投影支持 VGA 和 HDMI ,但是我的 MAC 电脑只有一个 MINI DP 接口,如何把 MINI DP 转成 VGA 或 HDMI ,那么我就卖了这个玩意「全称 MINI DP 转 VGA & HDMI 适配器」,这东西就是一个适配器

MINI DP 转 VGA 或 HDMI
MINI DP 转 VGA 或 HDMI

这个适配器就可以把 MAC 和有 VGA 或 HDMI 的设备连接起来了,如下:

适配器连接各个设备
适配器连接各个设备

类似的还有电脑电源适配器,变压器「也是一种适配器」,其实净水器也可以看作是一种适配器「把杂水变成纯净水」,等等

2、程序中的适配器

比如我们对接第三方的接口到我们的系统「对方给我们的接口,我们现在的接口对接不起来」

我们接口和三方接口
我们接口和三方接口

我们就需要写一个中间层「适配器」,做为一个桥梁,把两个接口连接起来

接口或系统之间的适配器
接口或系统之间的适配器

适配器模式的定义

通俗的说适配器模式就是把两个不兼容的接口连接起来,类似一个桥梁的作用

适配器模式类比
适配器模式类比

注:适配器模式类比一个桥梁作用「它的作用不仅仅连接这么简单,还有转化等操作,桥梁就是为了方便理解」

适配器模式的结构

角色 类别 说明
Target 目标角色 是一个接口,也就是我们期待要转化成的接口
Adaptee 源角色 原始的类或接口对象
Adapter 适配器角色 把源角色转化成目标角色的类

适配器模式的分类

  • 1、类适配器模式

类适配器简单的 UML

类适配器简单的 UML
类适配器简单的 UML

总结一下就是:适配器「Adapter」继承源类「Src」并且实现目标「Dst」接口,来实现 Src-->Dst 的转换

  • 2、对象适配器模式

对象适配器简单的 UML

对象适配器简单的 UML
对象适配器简单的 UML

总结一下就是:适配器「Adapter」持有源类「Src」的引用,并实现目标「Dst」接口,来实现 Src--> Dst 的转化

  • 3、接口适配器模式

对于这种模式「资料上也没有说有这种模式,我是在写代码的过程中发现可以这样写」,我持保留意见,如果有什么问题,大家完全可以说适配器模式的分类就有以上两种模式,可我认为这是适配器模式的一个变种

接口适配器简单的 UML

接口适配器
接口适配器

*总结一下就是: 适配器实现源和目标,把源转化成目标这么一个过程

二、适配器模式举例

1、Mac 电脑连接投影仪适配器

以开头的例子为例子, MAC 电脑要连接投影仪器,需要一个 MINI DP 转 VGA & HDMI 适配器,然后才能连接上投影仪

所以这里目标是 VGAORHDMI ,源是 MINI DP 适配器就是上面的那根线

类适配投影仪和 MAC 电脑简单的 UML

适配投影仪和 MAC 电脑简单的 UML
适配投影仪和 MAC 电脑简单的 UML

根据 UML 撸码

使用类适配器模式

  • 1、定义目标接口 VgaOrHdmi
/**
 * 目标角色,对投影仪来说就要 VAG 或 HDMI
 * @auther TigerChain
 */
public interface VgaOrHdmi {
    /**输出 VGA 或是 Hdmi 接口*/
    String getVgaOrHdmi() ;
}
  • 2、定义源类 MiniDp
/**
 * 源角色,MAC 电脑上的 MINIDP 接口
 * @auther TigerChain
 */
public class MiniDp {

    public String outPutMinkDp(){
        return "我是 mac 上的 MiniDp 输入接口" ;
    }
}
  • 3、定义适配器类 MidiDp2VgaOrHdmiAdapter
/**
 * 适配器,既是 MINIDP 接口也是 VAGORHDMI 接口,这样就可以把 MINIDP 转成
 * VAG OR HDMI 接口
 * @auther TigerChain
 */
public class MidiDp2VgaOrHdmiAdapter extends MiniDp implements VgaOrHdmi{

    @Override
    public String getVgaOrHdmi() {
       return  convertMiniDp2VgaOrHdmi() ;
    }
    /**
     * 把 MINIDP 转化成 VAG 或 HDMI 方法
     * @return
     */
    private String convertMiniDp2VgaOrHdmi(){
        //拿到源
        String str = outPutMinkDp();
        System.out.println(str+" \n 经过适配器转化 ");
        // 为这简单起见,这里直接修改源
        str = "输出变成  VGA 和 HDMI 接口" ;
        return str ;
    }
}
  • 4、定义打印机类 Projector
/**
 * 这是投影仪,我就是 VGA 和 HDMI 接口的
 * @auther TigerChain
 */
public class Projector {
        // 我要的就是 VGA 或者 HDMI 接口
        public String getVgaOrHdmi(VgaOrHdmi vgaOrHdmi){
            return vgaOrHdmi.getVgaOrHdmi() ;
        }
}
  • 5、定义测试类 Test
/**
 * 测试类
 * @auther TigerChain
 */
public class Test {
    public static void main(String args[]){
        //投影仪
        Projector projector = new Projector() ;
        //适配器
        VgaOrHdmi adapter = new MidiDp2VgaOrHdmiAdapter() ;
        // 最后得到投影仪想要的 VAG or HDMI 即可
        String str = projector.getVgaOrHdmi(adapter) ;
        System.out.println(str);
    }
}
  • 6、运行查看结果

类适配器实现 mini2vga
类适配器实现 mini2vga

完美转化了有木有

对象适配器实现上述例子

对象适配投影仪和 MAC 电脑简单的 UML

对象适配投影仪和 MAC 电脑简单的 UML
对象适配投影仪和 MAC 电脑简单的 UML

是不是和上面的图一样?错,肯定不一样,一样我还贴出来「我又没病」,只有一点改变,就是适配器不是继承源,而是持有源的引用,代码修改起来非常简单,只是修改适配器即可「别的代码都是一样的」

  • 1、修改 MidiDp2VgaOrHdmiAdapter
/**
 * 适配器,既是 MINIDP 接口也是 VAGORHDMI 接口,这样就可以把 MINIDP 转成
 * VAG OR HDMI 接口
 */
public class MidiDp2VgaOrHdmiAdapter  implements VgaOrHdmi{

    // 修改之处 1 
    private MiniDp miniDp ;
    // 修改之处 2
    public MidiDp2VgaOrHdmiAdapter(MiniDp miniDp){
        this.miniDp = miniDp ;
    }

    @Override
    public String getVgaOrHdmi() {
       return  convertMiniDp2VgaOrHdmi() ;
    }
    /**
     * 把 MINIDP 转化成 VAG 或 HDMI 方法
     * @return
     */
    private String convertMiniDp2VgaOrHdmi(){
        // 修改之处 3  拿到源
        String str = miniDp.outPutMinkDp();
        System.out.println(str+" \n 经过适配器转化 ");
        // 为这简单起见,这里直接修改源
        str = "输出变成  VGA 和 HDMI 接口" ;
        return str ;
    }
}
  • 2、修改测试类,并运行查看结果

修改测试类

对象适配器测试类
对象适配器测试类

结果和上面是一样的

对象适配器实现 mini2vga
对象适配器实现 mini2vga

适配器模式一般情况下不是软件设计的时候就要考虑的一种模式,它是一种随着软件的维护可能由于不同的开发人员,不同的产品,不同的厂家造成的功能类似而接口不相同的情况下一种解决方案「只有碰到无法改变原有设计和代码的情况下,才考虑适配」

2、成龙初探好莱坞

我们的功夫明星成龙初闯好莱坞的时候有一个最大的障碍就是语言问题「英文不太熟悉」,那么最早的时候都是有翻译者的,那么这个翻译员就充当了适配器的角色「把英文翻译成中文,或者把中文翻译成英文」

翻译员简单的 UML

翻译员简单的 UML
翻译员简单的 UML

根据 UML 擼代码

  • 1、新建 ISpeakEn 接口
/**
 * Created by tigerchain on 11/12/17.
 */
public interface ISpeakEn {
    // 说英文
    String speakEnglish(String str) ;
}
  • 2、新建 ISpeakCn 接口
/**
 * Created by tigerchain on 11/12/17.
 */
public interface ISpeakCn {
    // 说中文
    String speakCn(String str) ;
}
  • 3、翻译的接口「适配器」 Interpreter
/**
 * Created by tigerchain on 11/12/17.
 * 翻译的接口
 */
public interface Interpreter {

    // 中文翻译成英文
    void chinese2English(String str) ;
    // 英文翻译成中文
    void english2Chinese(String str) ;
}
  • 4、具体的翻译员小张 ZhangTranslation
/**
 * Created by tigerchain on 11/12/17.
 * 举个例子,成龙有一个张翻译,能把英文翻译成中文,也能把中文翻译成英文
 */
public class ZhangTranslation implements Iinterpreter,ISpeakCn,ISpeakEn{

    @Override
    public void chinese2English(String str) {
        translationC2E(speakCn(str));
    }

    @Override
    public void english2Chinese(String str) {
        translationE2C(str) ;
    }
    // 翻译英文--> 中文
    private void translationE2C(String str) {
        System.out.println("小张把 "+str+" 翻译成中文");
    }

    // 翻译中文--> 英文
    private void translationC2E(String str){
        System.out.println("小张把 "+str+" 翻译成英文");
    }
    @Override
    public String speakCn(String str) {
        return str ;
    }

    @Override
    public String speakEnglish(String str) {
        return str;
    }
}
  • 5、来一个老外「要对话肯定要有关建人物呀」 Foreigner
/**
 * Created by tigerchain on 11/12/17.
 * 一个老外用英文给成龙打招呼
 */
public class Foreigner implements ISpeakEn {

    @Override
    public String speakEnglish(String str) {
        String say = "Wills say:"+str ;
        System.out.println(say);
        return say ;
    }
}
  • 6、成龙上场「另一个关建人物」 JackieChan
/**
 * Created by tigerchain on 11/12/17.
 */
public class JackieChan implements ISpeakCn {

    @Override
    public String speakCn(String str) {
        String say = "成龙说:"+str ;
        System.out.println(say);
        return say ;
    }
}
  • 7、测试对话 Test
/**
 * Created by tigerchain on 11/12/17.
 * 这是一个成龙对话老外的测试类
 */
public class Test {

    public static void main(String args[]){
        // 成龙说了一句话
        JackieChan jackieChan = new JackieChan() ;
        String str = jackieChan.speakCn("你好 wills");

        // 老外说了一句
        Foreigner foreigner = new Foreigner() ;
        String str2 = foreigner.speakEnglish("Hello Jackie Chain");

        // 张翻译翻译
        ZhangTranslation zhangTranslation = new ZhangTranslation() ;
        zhangTranslation.chinese2English(str);
        zhangTranslation.english2Chinese(str2);
    }
}
  • 8、运行查看结果

成龙对话结果
成龙对话结果

怎么样这个张翻译「适配器」还不错吧,当然适配器模式也会进化,会变种,但是万变不离其宗「上面 Demo 就可以看作是一个变种的适配器模式」

三、Android 源码中的适配器模式

ListAdapter

没有搞错吧,上一节不是说了 ListAdapter 是一种策略模式吗?没错它也是一种适配器模式「从名字就可以看出来」

ListAdapter 适配器简单的 UML

ListAdapter 适配器 UML
ListAdapter 适配器 UML

从上图可以看出,BaseAdapter 是一个基础适配器,下面子类是具体各自的适配器,这些适配器的作用就是把数据 List,Cusor 等转化成 ListAdapter 接口,最终让客户端 ListView 来调用「可以通俗的说就是把数据适配到 View 上面」

以 ArrayAdapter<T> 源码分析一下

  • 1、先看看 Adapter
public interface Adapter {
int getCount();

    Object getItem(int var1);

    long getItemId(int var1);

    boolean hasStableIds();

    View getView(int var1, View var2, ViewGroup var3);

    int getItemViewType(int var1);

    int getViewTypeCount();

    boolean isEmpty();
 }
  • 2、ListAdapter 继承 Adapter 接口,所以拥有 Adapter 所有功能
  • 3、BaseAdapter 实现 ListAdapter 所以不仅拥有 ListAdapteer 的所有能力
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter{

    //省略苦干代码
    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }

    public boolean isEmpty() {
        return getCount() == 0;
    }    
}
  • 3、再来看看 ArrayAdapter
public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {

 private List<T> mObjects;  
    //列出其中一个构造方法 
     public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId) {
        this(context, resource, textViewResourceId, new ArrayList<>());
    }

    @Override
    public int getCount() {
        return mObjects.size();
    }

    @Override
    public @Nullable T getItem(int position) {
        return mObjects.get(position);
    }

    public int getPosition(@Nullable T item) {
        return mObjects.indexOf(item);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public @NonNull View getView(int position, @Nullable View convertView,
            @NonNull ViewGroup parent) {
        return createViewFromResource(mInflater, position, convertView, parent, mResource);
    }
    //其它代码流省略
}

我们通过源码可以看到 ArrayAdapter 就是把 List 的数据源采用一系列方法转化成 ListAdapter 需要的几种方法 getView getPosition 等等「这就是一个适配的过程」

ListAdapter 既是策略模式又是适配器模式

根据选择模式使用那种适配器 ListAdapter 就是策略模式,但是根据每个策略所实现功能「它就是适配器模式」

四、适配器模式的优缺点

优点

  • 1、客户端只关心适配器,对客户端来说更简单
  • 2、现有类的复用而不需要改变,解决了现有类和目标类环境不一致的问题
  • 3、解耦「目标类和适配器解耦」,不用改变原有的代码,再一个就是某天目标大变了,那么我们再编写一个适配器就可以了「原来的适配器可以扔掉了,就像某天你的 MAC 笔记本坏了,电源适配器就可以扔了--这是一个玩笑,除非是适配器不适用新买的 MAC」

缺点

  • 1、适配器编写过程需要多方考虑「可能会很复杂」
  • 2、适配器把一个接口转化成另一个接口,在客户端会给人误导,明明传入的是 A 接口,最后成 B 了,让人很晕

到此为止,我们就介绍完了适配器模式,点赞是一种美德