04、适配器模式--Adapter

本节大纲

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

教程简介

正文

一、什么是适配器模式

1、生活中的适配器

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

MINI DP 转 VGA 或 HDMI

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

适配器连接各个设备

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

2、程序中的适配器

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

我们接口和三方接口

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

接口或系统之间的适配器

适配器模式的定义

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

适配器模式类比

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

适配器模式的结构

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

适配器模式的分类

  • 1、类适配器模式

类适配器简单的 UML

类适配器简单的 UML

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

  • 2、对象适配器模式

对象适配器简单的 UML

对象适配器简单的 UML

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

  • 3、接口适配器模式

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

接口适配器简单的 UML

接口适配器

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

二、适配器模式举例

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

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

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

类适配投影仪和 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

完美转化了有木有

对象适配器实现上述例子

对象适配投影仪和 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

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

2、成龙初探好莱坞

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

翻译员简单的 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

从上图可以看出,BaseAdapter 是一个基础适配器,下面子类是具体各自的适配器,这些适配器的作用就是把数据 List<T>,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<T>
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<T> 就是把 List<T> 的数据源采用一系列方法转化成 ListAdapter 需要的几种方法 getView getPosition 等等「这就是一个适配的过程」

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

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

四、适配器模式的优缺点

优点

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

缺点

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342