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

适配器模式大纲

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

教程简介ide

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

正文源码分析

1、什么是适配器模式

一、生活中的适配器

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

MINI DP 转 VGA 或 HDMI

这个适配器就能够把 MAC 和有 VGA 或 HDMI 的设备链接起来了,以下:this

适配器链接各个设备

相似的还有电脑电源适配器,变压器「也是一种适配器」,其实净水器也能够看做是一种适配器「把杂水变成纯净水」,等等spa

二、程序中的适配器

好比咱们对接第三方的接口到咱们的系统「对方给咱们的接口,咱们如今的接口对接不起来」翻译

咱们接口和三方接口

咱们就须要写一个中间层「适配器」,作为一个桥梁,把两个接口链接起来设计

接口或系统之间的适配器

适配器模式的定义code

通俗的说适配器模式就是把两个不兼容的接口链接起来,相似一个桥梁的做用对象

适配器模式类比

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

适配器模式的结构

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

适配器模式的分类

  • 一、类适配器模式

类适配器简单的 UML

类适配器简单的 UML

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

  • 二、对象适配器模式

对象适配器简单的 UML

对象适配器简单的 UML

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

  • 三、接口适配器模式

对于这种模式「资料上也没有说有这种模式,我是在写代码的过程当中发现能够这样写」,我持保留意见,若是有什么问题,你们彻底能够说适配器模式的分类就有以上两种模式,可我认为这是适配器模式的一个变种

接口适配器简单的 UML

接口适配器

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

2、适配器模式举例

一、Mac 电脑链接投影仪适配器

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

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

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

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

根据 UML 撸码

使用类适配器模式

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

    public String outPutMinkDp(){
        return "我是 mac 上的 MiniDp 输入接口" ;
    }
}
  • 三、定义适配器类 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 ;
    }
}
  • 四、定义打印机类 Projector
/**
 * 这是投影仪,我就是 VGA 和 HDMI 接口的
 * @auther TigerChain
 */
public class Projector {
        // 我要的就是 VGA 或者 HDMI 接口
        public String getVgaOrHdmi(VgaOrHdmi vgaOrHdmi){
            return vgaOrHdmi.getVgaOrHdmi() ;
        }
}
  • 五、定义测试类 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);
    }
}
  • 六、运行查看结果

类适配器实现 mini2vga

完美转化了有木有

对象适配器实现上述例子

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

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

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

  • 一、修改 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 ;
    }
}
  • 二、修改测试类,并运行查看结果

修改测试类

对象适配器测试类

结果和上面是同样的

对象适配器实现 mini2vga

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

二、成龙初探好莱坞

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

翻译员简单的 UML

翻译员简单的 UML

根据 UML 擼代码

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

    // 中文翻译成英文
    void chinese2English(String str) ;
    // 英文翻译成中文
    void english2Chinese(String str) ;
}
  • 四、具体的翻译员小张 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;
    }
}
  • 五、来一个老外「要对话确定要有关建人物呀」 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 ;
    }
}
  • 六、成龙上场「另外一个关建人物」 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 ;
    }
}
  • 七、测试对话 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);
    }
}
  • 八、运行查看结果

成龙对话结果

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

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

ListAdapter

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

ListAdapter 适配器简单的 UML

ListAdapter 适配器 UML

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

以 ArrayAdapter<T> 源码分析一下

  • 一、先看看 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();
 }
  • 二、ListAdapter 继承 Adapter 接口,因此拥有 Adapter 全部功能
  • 三、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;
    }    
}
  • 三、再来看看 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 就是策略模式,可是根据每一个策略所实现功能「它就是适配器模式」

4、适配器模式的优缺点

优势

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

缺点

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

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

公众号:TigerChain 更多精彩的文章等着你
TigerChain

相关文章
相关标签/搜索