热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要介绍了Android设计模式之适配器(Adapter)模式,以源码解析的方式分析适配器模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了Android适配器模式源码,供大家参考,具体内容如下

1. 模式介绍

1.1模式的定义:

  适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

1.2模式的使用场景:

  用电源接口做例子,笔记本电脑的电源一般都是接受5V的电压,但是我们生活中的电线电压一般都是220V的输出。这个时候就出现了不匹配的状况,在软件开发中我们称之为接口不兼容,此时就需要适配器来进行一个接口转换。在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个层我们可以理解为这里的Adapter层,通过这层来进行一个接口转换就达到了兼容的目的。   

2.模式的简单实现

2.1简单实现的介绍:

  在上述电源接口这个示例中,5V电压就是Target接口,220v电压就是Adaptee类,而将电压从220V转换到5V就是Adapter。

2.2类适配器模式:

 /**
 * Target角色
 */
public interface FiveVolt {
  public int getVolt5();
}

/**
 * Adaptee角色,需要被转换的对象
 */
public class Volt220 {
  public int getVolt220() {
    return 220;
  }
}

// adapter角色
public class ClassAdapter extends Volt220 implements FiveVolt {

  @Override
  public int getVolt5() {
    return 5;
  }

}

Target角色给出了需要的目标接口,而Adaptee类则是需要被转换的对象。Adapter则是将Volt220转换成Target的接口。对应的是Target的目标是要获取5V的输出电压,而Adaptee即正常输出电压是220V,此时我们就需要电源适配器类将220V的电压转换为5V电压,解决接口不兼容的问题。

public class Test {
  public static void main(String[] args) {
    ClassAdapter adapter = new ClassAdapter();
    System.out.println("输出电压 : " + adapter.getVolt5());
  }
}

2.3.Android源码中的模式实现

与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用代理关系连接到Adaptee类。

从图2可以看出,Adaptee类 ( Volt220 ) 并没有getVolt5()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。

/**
 * Target角色
 */
public interface FiveVolt {
  public int getVolt5();
}

/**
 * Adaptee角色,需要被转换的对象
 */
public class Volt220 {
  public int getVolt220() {
    return 220;
  }
}

// 对象适配器模式
public class ObjectAdapter implements FiveVolt {

  Volt220 mVolt220;

  public ObjectAdapter(Volt220 adaptee) {
    mVolt220 = adaptee;
  }

  public int getVolt220() {
    return mVolt220.getVolt220();
  }

  @Override
  public int getVolt5() {
    return 5;
  }

}

2.4.类适配器和对象适配器的权衡

*类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。

*对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理Adaptee的子类了。对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。

*对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。

*对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。对于对象适配器,需要额外的引用来间接得到Adaptee。

建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。

3.Android ListView中的Adapter模式

在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下:

  // 适配器
public class MyAdapter extends BaseAdapter{

    private LayoutInflater mInflater;
    List mDatas ;

    public MyAdapter(Context context, List datas){
      this.mInflater = LayoutInflater.from(context);
      mDatas = datas ;
    }
    @Override
    public int getCount() {
      return mDatas.size();
    }

    @Override
    public String getItem(int pos) {
      return mDatas.get(pos);
    }

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

    // 解析、设置、缓存convertView以及相关内容
    @Override
    public View getView(int position, View convertView, ViewGroup parent) { 
      ViewHolder holder = null;
      // Item View的复用
      if (cOnvertView== null) {
        holder = new ViewHolder(); 
        cOnvertView= mInflater.inflate(R.layout.my_listview_item, null);
        // 获取title
        holder.title = (TextView)convertView.findViewById(R.id.title);
        convertView.setTag(holder);
      } else {
        holder = (ViewHolder)convertView.getTag();
      }
      holder.title.setText(mDatas.get(position));
      return convertView;
    }

  }

这看起来似乎还挺麻烦的,看到这里我们不禁要问,ListView为什么要使用Adapter模式呢?
我们知道,作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。

Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。
通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。

那么ListView是如何通过Adapter模式 ( 不止Adapter模式 )来运作的呢 ?我们一起来看一看。
ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。

public abstract class AbsListView extends AdapterView implements TextWatcher,
    ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
    ViewTreeObserver.OnTouchModeChangeListener,
    RemoteViewsAdapter.RemoteAdapterConnectionCallback {

  ListAdapter mAdapter ;

  // 关联到Window时调用的函数
  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    // 代码省略
    // 给适配器注册一个观察者,该模式下一篇介绍。
    if (mAdapter != null && mDataSetObserver == null) {
      mDataSetObserver = new AdapterDataSetObserver();
      mAdapter.registerDataSetObserver(mDataSetObserver);

      // Data may have changed while we were detached. Refresh.
      mDataChanged = true;
      mOldItemCount = mItemCount
      // 获取Item的数量,调用的是mAdapter的getCount方法
      mItemCount = mAdapter.getCount();
    }
    mIsAttached = true;
  }

  /**
   * 子类需要覆写layoutChildren()函数来布局child view,也就是Item View
   */
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    mInLayout = true;
    if (changed) {
      int childCount = getChildCount();
      for (int i = 0; i 

AbsListView定义了集合视图的框架,比如Adapter模式的应用、复用Item View的逻辑、布局Item View的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如ListView。

ListView中的相关方法。

  @Override
  protected void layoutChildren() {
    // 代码省略

    try {
      super.layoutChildren();
      invalidate();
      // 代码省略
      // 根据布局模式来布局Item View
      switch (mLayoutMode) {
      case LAYOUT_SET_SELECTION:
        if (newSel != null) {
          sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
        } else {
          sel = fillFromMiddle(childrenTop, childrenBottom);
        }
        break;
      case LAYOUT_SYNC:
        sel = fillSpecific(mSyncPosition, mSpecificTop);
        break;
      case LAYOUT_FORCE_BOTTOM:
        sel = fillUp(mItemCount - 1, childrenBottom);
        adjustViewsUpOrDown();
        break;
      case LAYOUT_FORCE_TOP:
        mFirstPosition = 0;
        sel = fillFromTop(childrenTop);
        adjustViewsUpOrDown();
        break;
      case LAYOUT_SPECIFIC:
        sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
        break;
      case LAYOUT_MOVE_SELECTION:
        sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
        break;
      default:
        // 代码省略
        break;
      }

  }

  // 从上到下填充Item View [ 只是其中一种填充方式 ]
  private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
      end -= mListPadding.bottom;
    }

    while (nextTop 

ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。

当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。

通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。

4.杂谈

优点与缺点

优点

更好的复用性
  系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

更好的扩展性
  在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 本文介绍如何在 Android 中通过代码模拟用户的点击和滑动操作,包括参数说明、事件生成及处理逻辑。详细解析了视图(View)对象、坐标偏移量以及不同类型的滑动方式。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
author-avatar
冰_若真人_755_171
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有