Android设计模式之适配器模式和应用场景详解

移动开发 Android
而在android开发中,必要的了解一些设计模式又是必须的,因为设计模式在Android源码中,可以说是无处不在。今天我们来讲解适配器模式。

[[417879]]

前言

设计模式有时候就是一道坎,但是设计模式又非常有用,过了这道坎,它可以让你水平提高一个档次。而在android开发中,必要的了解一些设计模式又是必须的,因为设计模式在Android源码中,可以说是无处不在。

今天我们来讲解适配器模式

一、适配器模式的定义和解决问题

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

2、是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能

3、将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

4、这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡;

5、主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的;

二、适用场景和优缺点

1、使用场景

  • 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容;
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的一些类一起工作;
  • 需要一个统一的输出接口,而输入端的接口不可预知;

2、优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便的更换适配器,也可以在不修改原有代码的基础上 增加新的适配器,完全符合开闭原则。

3、缺点

  • 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
  • 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
  • 一次最多只能适配一个适配者类,不能同时适配多个适配者。
  • 目标抽象类只能为接口,不能为类,其使用有一定的局限性;

三、适配器两种模式

适配器模式有两种:

  • 类适配器
  • 对象适配器

模式所涉及的角色有:

  • 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
  • 源(Adapee)角色:现在需要适配的接口。
  • 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

图片

场景:

假如A类想用M方法,X类有M方法,但是M方法的结果不一定完全符合A类的需求

那么X类就是写死了,不好用,这样设计不好

那就把X类换成一个接口,弄出一些B,C,D,E.....类中间类出来,让他们都有一个方法来处理M方法的东西,再给A类用

1、类适配器:

设计一个接口I,让他也有M方法

然后设计一个B类,写好符合A类需求的specialM方法

然后让A类继承B类,并实现I接口的M方法

最后在A类的M方法中以super的方式调用B类的specialM方法

2、对象适配器:(更多是用对象适配器)

设计一个接口I,让他也有M方法

然后设计一个B类,写好符合A类需求的specialM方法

然后在A类中声明一个B类变量,并且A类实现I接口,那么A类也就有了M方法

最后在A类的M方法中,如果需要,就可以选择调用B类的specialM方法

或者设计一个B类,实现I接口的M方法

然后在A类中声明一个I类变量,再直接调用I接口的M方法

在调用A类的M方法之前,通过例如setAdapter(I Adapter)这样的方法,将B类设置成A类的成员变量

这样就保证了A类和I接口不变,适配不同情况的时候,写一个类似B类的中间类进行适配就可以了

总之,两端不变,通过不同的选择方式,选择不同的中间类,也就是适配器模式了

三、现实中适配器案例

实现

这里我们通过一个实例来模拟一下适配器模式。需求是这样的:IPhone12的耳机口被取消,我们怎么保证之前的耳机还能用呢?当然是需要一个转接头了,这个转接头呢,其实就类似我们的适配器。

耳机需要的接口就是我们的目标角色,手机提供的接口就是我们的源角色,转接头当然就是适配器角色了。

类适配器

目标角色

  1. public interface ITarget { 
  2.     //获取需要的接口 
  3.     String getRightInterface(); 

源角色

  1. public class IPhoneSeven { 
  2.     //获取iphone7提供的接口 
  3.     public String getInterface(){ 
  4.         return "iphone7 interface"
  5.     } 

适配器

  1. public class CAdapter extends IPhoneSeven implements ITarget{ 
  2.     @Override 
  3.     public String getRightInterface() { 
  4.         String newInterface = getInterface(); 
  5.         return suit(newInterface); 
  6.     } 
  7.     /** 
  8.      * 转换操作 
  9.      * @param newInterface 
  10.      * @return 
  11.      */ 
  12.     private String suit(String newInterface) { 
  13.         return "3.5mm interface"
  14.     } 

对象适配器

对象适配器的目标角色和源角色是一样的,我们就不再写了。

适配器

  1. public class Adapter implements ITarget { 
  2.     private IPhoneSeven mIPhoneSeven; 
  3.     public Adapter(IPhoneSeven IPhoneSeven) { 
  4.         mIPhoneSeven = IPhoneSeven; 
  5.     } 
  6.     @Override 
  7.     public String getRightInterface() { 
  8.         String newInterface = mIPhoneSeven.getInterface(); 
  9.         return suit(newInterface); 
  10.     } 
  11.     /** 
  12.      * 转换操作 
  13.      * @param newInterface 
  14.      * @return 
  15.      */ 
  16.     private String suit(String newInterface) { 
  17.         return "3.5mm interface"
  18.     } 

四、Android中的应用场景

适配器模式在android中的应用非常广,最常见的ListView、GridView、RecyclerView等的Adapter。而,我们经常使用的ListView就是一个典范。

在使用ListView时,每一项的布局和数据都不一样,但是最后输出都可以看作是一个View,这就对应了上面的适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的接口不可预知。下面我们来看看ListView中的适配器模式。

首先我们来看看一般我们的Adapter类的结构

  1. class Adapter extends BaseAdapter { 
  2.     private List<String> mDatas; 
  3.     public Adapter(List<String> datas) { 
  4.         mDatas = datas; 
  5.     } 
  6.     @Override 
  7.     public int getCount() { 
  8.         return mDatas.size(); 
  9.     } 
  10.     @Override 
  11.     public long getItemId(int position) { return position; } 
  12.     @Override 
  13.     public Object getItem(int position) { return mDatas.get(position);} 
  14.     @Override 
  15.     public View getView(int position, View convertView, ViewGroup parent) { 
  16.         if (convertView == null) { 
  17.             //初始化View 
  18.         } 
  19.         //初始化数据 
  20.         return convertView; 
  21.     } 

可以看出Adapter里面的接口主要是getCount()返回子View的数量,以及getView()返回我们填充好数据的View,ListView则通过这些接口来执行具体的布局、缓存等工作。下面我们来简单看看ListView的实现。

首先这些getCount()等接口都在一个接口类Adapter里

  1. public interface Adapter { 
  2.     //省略其他的接口 
  3.     int getCount();  
  4.     Object getItem(int position); 
  5.     long getItemId(int position); 
  6.     View getView(int position, View convertView, ViewGroup parent); 
  7.     //省略其他的接口 
  8. 中间加了一个过渡的接口ListAdapter 
  9. public interface ListAdapter extends Adapter { 
  10.     //接口省略 

我们在编写我们自己的Adapter时都会继承一个BaseAdapter,我们来看看BaseAdapter

  1. public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { 
  2.     //BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口 
  3.     //而像getCount()以及getView()这些接口则需要我们自己去实现 
  4. ListView的父类AbsListView中有ListAdapter接口,通过这个接口来调用getCount()等方法获取View的数量等 
  5. public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, 
  6.         ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, 
  7.         ViewTreeObserver.OnTouchModeChangeListener, 
  8.         RemoteViewsAdapter.RemoteAdapterConnectionCallback { 
  9.     /** 
  10.      * The adapter containing the data to be displayed by this view 
  11.      */ 
  12.     ListAdapter mAdapter; 
  13.     @Override 
  14.     protected void onAttachedToWindow() { 
  15.         super.onAttachedToWindow(); 
  16.         final ViewTreeObserver treeObserver = getViewTreeObserver(); 
  17.         treeObserver.addOnTouchModeChangeListener(this); 
  18.         if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) { 
  19.             treeObserver.addOnGlobalLayoutListener(this); 
  20.         } 
  21.         if (mAdapter != null && mDataSetObserver == null) { 
  22.             mDataSetObserver = new AdapterDataSetObserver(); 
  23.             mAdapter.registerDataSetObserver(mDataSetObserver); 
  24.             // Data may have changed while we were detached. Refresh. 
  25.             mDataChanged = true
  26.             mOldItemCount = mItemCount; 
  27.             //通过getCount()获取View元素的个数 
  28.             mItemCount = mAdapter.getCount(); 
  29.         } 
  30.     } 

从上面我们可以看出,AbsListView是一个抽象类,它里面封装了一些固定的逻辑,如Adapter模式的应用逻辑、布局的复用逻辑和布局子元素逻辑等。而具体的实现则是在子类ListView中。下面我们来看看ListView中是怎么处理每一个子元素View的。

  1. @Override 
  2. protected void layoutChildren() { 
  3.     //省略其他代码 
  4.     case LAYOUT_FORCE_BOTTOM: 
  5.         sel = fillUp(mItemCount - 1, childrenBottom); 
  6.         adjustViewsUpOrDown(); 
  7.         break; 
  8.     case LAYOUT_FORCE_TOP: 
  9.         mFirstPosition = 0; 
  10.         sel = fillFromTop(childrenTop); 
  11.         adjustViewsUpOrDown(); 
  12.         break; 
  13.     //省略其他代码 

在ListView中会覆写AbsListView中的layoutChildren()函数,在layoutChildren()中会根据不同的情况进行布局,比如从上到下或者是从下往上。下面我们看看具体的布局方法fillUp方法。

  1. private View fillUp(int pos, int nextBottom) { 
  2.     //省略其他代码 
  3.     while (nextBottom > end && pos >= 0) { 
  4.         // is this the selected item? 
  5.         boolean selected = pos == mSelectedPosition; 
  6.         View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected); 
  7.         nextBottom = child.getTop() - mDividerHeight; 
  8.         if (selected) { 
  9.             selectedView = child; 
  10.         } 
  11.         pos--; 
  12.     } 
  13.     mFirstPosition = pos + 1; 
  14.     setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); 
  15.     return selectedView; 

这里我们看到fillUp方法里面又会通过makeAndAddView()方法来获取View,下面我们来看看makeAndAddView()方法的实现。

  1. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, 
  2.             boolean selected) { 
  3.     if (!mDataChanged) { 
  4.         // Try to use an existing view for this position. 
  5.         final View activeView = mRecycler.getActiveView(position); 
  6.         if (activeView != null) { 
  7.             // Found it. We're reusing an existing child, so it just needs 
  8.             // to be positioned like a scrap view
  9.             setupChild(activeView, position, y, flow, childrenLeft, selected, true); 
  10.             return activeView; 
  11.         } 
  12.     } 
  13.     // Make a new view for this position, or convert an unused view if 
  14.     // possible. 
  15.     final View child = obtainView(position, mIsScrap); 
  16.     // This needs to be positioned and measured. 
  17.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); 
  18.     return child; 

不知道大家看到这里想到了什么?

makeAndAddView()方法里面就出现了缓存机制了,这是提升ListView加载效率的关键方法。我们看到,在获取子View时会先从缓存里面找,也就是会从mRecycler中找,mRecycler是AbsListView中的一个用于缓存的RecycleBin类,来,我们看看缓存的实现。

  1. class RecycleBin { 
  2.     private View[] mActiveViews = new View[0]; 
  3.     /** 
  4.      * Get the view corresponding to the specified position. The view will be removed from 
  5.      * mActiveViews if it is found. 
  6.      * 
  7.      * @param position The position to look up in mActiveViews 
  8.      * @return The view if it is found, null otherwise 
  9.      */ 
  10.     View getActiveView(int position) { 
  11.         int index = position - mFirstActivePosition; 
  12.         final View[] activeViews = mActiveViews; 
  13.         if (index >=0 && index < activeViews.length) { 
  14.             final View match = activeViews[index]; 
  15.             activeViews[index] = null
  16.             return match; 
  17.         } 
  18.         return null
  19.     } 

由上可见,缓存的View保存在一个View数组里面,然后我们来看看如果没有找到缓存的View,ListView是怎么获取子View的,也就是上面的obtainView()方法。需要注意的是obtainView()方法是在AbsListView里面。

  1. View obtainView(int position, boolean[] outMetadata) { 
  2.     //省略其他代码 
  3.     final View scrapView = mRecycler.getScrapView(position); 
  4.     final View child = mAdapter.getView(position, scrapView, this); 
  5.     if (scrapView != null) { 
  6.         if (child != scrapView) { 
  7.             // Failed to re-bind the data, return scrap to the heap. 
  8.             mRecycler.addScrapView(scrapView, position); 
  9.         } else if (child.isTemporarilyDetached()) { 
  10.             outMetadata[0] = true
  11.             // Finish the temporary detach started in addScrapView(). 
  12.             child.dispatchFinishTemporaryDetach(); 
  13.         } 
  14.     } 
  15.     //省略其他代码 
  16.     return child; 

可以看到没有缓存的View直接就是从我们编写的Adapter的getView()方法里面获取。

以上我们简单看了ListView中适配器模式的应用,从中我们可以看出ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口莫过于getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化。

[[417880]]

总结:

  • 更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
  • 更好的扩展性:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

本文转载自微信公众号「 Android开发编程」

 

责任编辑:姜华 来源: Android开发编程
相关推荐

2012-05-16 17:22:11

Java设计模式

2021-02-18 08:39:28

设计模式场景

2013-11-26 16:39:21

Android设计模式

2020-10-25 08:56:21

适配器模式

2012-04-12 09:33:02

JavaScript

2022-02-13 23:33:24

设计模式Java

2022-02-18 17:21:29

适配器模式客户端

2024-07-31 10:41:16

C#设计模式

2024-02-22 12:13:49

适配器模式代码

2021-08-11 17:15:17

AndroidActivity场景

2013-02-26 10:55:47

C#适配器设计模式

2024-04-10 12:27:43

Python设计模式开发

2022-12-12 09:20:59

适配器模式接口

2012-08-02 10:46:34

JavaAdapter模式

2014-07-17 10:55:10

Win8.1应用开发适配器模式

2009-11-18 18:08:20

PHP适配器模式

2022-05-29 22:55:00

适配器设计模式

2021-04-27 08:31:10

前端应用场景

2023-08-15 11:07:37

适配器模式TypeScript

2021-02-16 08:16:09

适配器模式MybatisJava
点赞
收藏

51CTO技术栈公众号