Android设计模式—适配器模式★★★

在开发过程中是不是遇到过这样的问题:厂商给你的接口和你现有的接口对接不起来、旧的数据和新的数据接对接不起来等等。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

解决这种问题,第一个解决办法是修改各自类的接口,但是如果没有源代码或者不愿意为了一个应用而修改各自的接口,此时怎么办?这种情况往往会使用一个Adapter,在这两种接口之间创建一个“混血儿”接口,这个Adapter会将这两个接口进行兼容,在不改变原来两个接口的情况下,完全又写了一个类,做个中间人,就像翻译官一样会两国语言,你们说话都经过我来就行了。这个适配器实现了期望的接口,而且这个类也能和厂商的接口沟通。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

在java中也一样,就是让两个需要一起协调的类,通过适配器协调起来,也就是将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。简单概括就是:用“既有内容”去实现“需要结果”。

1.适配器模式

①适配器模式的定义:

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

②使用场景

1)系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容。

2)想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

3)需要一个统一的输出接口,而输入端的类型不可预知。

③根据适配器模式的定义,可以知道有三个角色参与了其中的工作:

1)Target(适配器接口):即目标角色,定义把其他类转换为何种接口,也就是客户端期望的接口。

2)Adaptee(被适配角色):即源角色,一般是已存在的类,需要适配新的接口。

3)Adapter(具体适配器):实现适配器接口,把源角色接口转换为目标角色期望的接口。

④适配器模式的优缺点:

优点:

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

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

3)增加了类的透明性和复用性,将具体的实现封装在适配器中,对于客户端来说是透明的,而且提高了适配者的复用性。

缺点:

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

2.UML类图

适配器模式分为两种:类适配器模式(继承)和对象适配器模式(委托)

①首先看类适配器模式,结构图如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_15,color_FFFFFF,t_70,g_se,x_16

类适配器是通过实现Target接口以及继承Adaptee类来实现接口转换。例如,目标接口需要的是operation2,但是Adaptee对象只有一个operation3,因此就出现了不兼容的情况。此时通过Adapter实现一个operation2函数将Adaptee的operation3转换为Target需要的operation2,以此实现兼容。

②对象适配器模式

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

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_15,color_FFFFFF,t_70,g_se,x_16

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

3.简单示例

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

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

①类适配器模式

在类适配器模式中,Target是一个接口,而不能是类。Adapter必须是具体的类,不能是接口。

 // Target接口

public interface FiveVolt {   

    public int getVolt5();

}

 // Adaptee角色,需要被转换的对象

public class Volt220 { 

    public int getVolt220() {    

          return 220;    

    }

}

// Adapter角色,将220V的电压转换成5V的电压

public class VoltAdapter extends Volt220 implements FiveVolt {    

   @Override   

   public int getVolt5() {

        return getVolt220() * 0 + 5;    

    }

}

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

public class Test {

    public static void main(String[] args) {

        VoltAdapter adapter = new VoltAdapter();

        System.out.println("输出电压 : " + adapter.getVolt5());    

    }

}

②对象适配器模式

/** * Target角色 */

public interface FiveVolt {

    public int getVolt5();

}

/** * Adaptee角色,需要被转换的对象 */

public class Volt220 {

    public int getVolt220() {

        return 220;    

    }

}

// 对象适配器模式

public class VoltAdapter implements FiveVolt {

   Volt220 mVolt220;    

   public VoltAdapter(Volt220 adaptee) {

        mVolt220 = adaptee;    

    }    

    @Override    

   public int getVolt5() {

        return mVolt220.getVolt220() * 0 + 5; 

    }

}

使用示例如下:

public class Test {

    public static void main(String[] args) {

        VoltAdapter adapter = new VoltAdapter(new Volt220());        

        System.out.println("输出电压 : " + adapter.getVolt5());

    }

}

4.类适配器模式和对象适配器模式比较

①类适配器采用了继承的方式来实现,而对象适配器是通过传递对象来实现,这是一种组合的方式。

②类适配器由于采用了继承,可以重写父类的方法;对象适配器则不能修改对象本身的方法等。

③适配器通过继承都获得了父类的方法,客户端使用时都会把这些方法暴露出去,增加了一定的使用成本;对象适配器则不会。

④类适配器只能适配他的父类,这个父类的其他子类都不能适配到;而对象适配器可以适配不同的对象,只要这个对象的类型是同样的。

⑤类适配器不需要额外的引用;对象适配器需要额外的引用来保存对象。

5.适配器模式在Android源码中的应用

适配器模式在android中的应用非常广,最常见的比如Listview、GridView、RecyclerView等的Adapter。

以Listview为例。

Listview用于显示列表数据,每一项的布局和数据都不一样,但是最后输出都可以看做是一个View,那么为了处理和显示不同的数据,就需要对应的适配器作为桥梁。这正好对应了适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的类型不可预知。

首先来看看在Listview里使用Adapter类的结构:

class Adapter extends BaseAdapter {

    private List<String> mDatas;

    public Adapter(List<String> datas) {

        mDatas = datas;

    } 

    @Override

    public int getCount() {

        return mDatas.size();

    }

    @Override

    public long getItemId(int position) {

        return position;

    }

    @Override

    public Object getItem(int position) {

        return mDatas.get(position);

    }

    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {

            //初始化View

        }

        //初始化数据

        return convertView;

    }

}

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

下面来简单看看ListView的实现。

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

public interface Adapter {

    void registerDataSetObserver( DataSetObserver observer);

    void unregisterDataSetObserver( DataSetObserver observer);

    int getCount(); 

    Object getItem(int position);

    long getItemId(int position);

    View getView(int position, View convertView, ViewGroup parent);

    int getItemViewType(int position);

    int getViewTypeCount();

}

中间加了一个过渡的接口ListAdapter:

public interface ListAdapter extends Adapter {

    public boolean areAllItemEnabled();

    boolean isEnabled(int position);

}

通常在编写自己的Adapter时都会继承一个BaseAdapter,来看看BaseAdapter:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { 

    //BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口,而像getCount()以及getView()这些接口则需要我们自己去实现

}

ListView的父类AbsListView中有ListAdapter接口,通过这个接口来调用getCount()等方法获取View的数量等:

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

    ListAdapter mAdapter; 

    @Override

    protected void onAttachedToWindow() {

        super.onAttachedToWindow(); 

        final ViewTreeObserver treeObserver = getViewTreeObserver();

      treeObserver.addOnTouchModeChangeLi stener( this);

        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {

            treeObserver.addOnGlobalLayoutListener( this);

        }

        if (mAdapter != null && mDataSetObserver == null) {

            mDataSetObserver = new AdapterDataSetObserver();

            mAdapter.registerDataSetObserver( mDataSetObserver);

            // Data may have changed while we were detached. Refresh.

            mDataChanged = true;

            mOldItemCount = mItemCount;

            //通过getCount()获取View元素的个数

            mItemCount = mAdapter.getCount();

        }

    }

}

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

@Override

protected void layoutChildren() {

    //……

    case LAYOUT_FORCE_BOTTOM:

        sel = fillUp(mItemCount - 1, childrenBottom);

        adjustViewsUpOrDown();

        break;

    case LAYOUT_FORCE_TOP:

        mFirstPosition = 0;

        sel = fillFromTop(childrenTop);

        adjustViewsUpOrDown();

        break;

    //……

}

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

private View fillUp(int pos, int nextBottom) {

    //……

    while (nextBottom > end && pos >= 0) {

        boolean selected = pos == mSelectedPosition;

        View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);

        nextBottom=child.getTop() - mDividerHeight;

        if (selected) {

            selectedView = child;

        }

        pos--;

    }

    mFirstPosition = pos + 1;

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);

    return selectedView;

}

fillUp方法里面会通过makeAndAddView()方法来获取View,下面来看看makeAndAddView()方法的实现:

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) {

    if (!mDataChanged) {

        final View activeView = mRecycler.getActiveView(position);

        if (activeView != null) {

            setupChild(activeView, position, y, flow, childrenLeft, selected, true);

            return activeView;

        }

    }

    final View child = obtainView(position, mIsScrap);

    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;

}

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

class RecycleBin {

    private View[] mActiveViews = new View[0];

    View getActiveView(int position) {

        int index = position - mFirstActivePosition;

        final View[] activeViews = mActiveViews;

        if (index >=0 && index < activeViews.length){

            final View match = activeViews[index];

            activeViews[index] = null;

            return match;

        }

        return null;

    }

}

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

View obtainView(int position, boolean[] outMetadata) {

    //……

    final View scrapView = mRecycler.getScrapView(position);

    final View child = mAdapter.getView(position, scrapView, this);

    if (scrapView != null) {

        if (child != scrapView) {

            mRecycler.addScrapView(scrapView, position);

        } else if (child.isTemporarilyDetached()) {

            outMetadata[0] = true;

            child.dispatchFinishTemporaryDetach();

        }

    }

    //……

    return child;

}

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

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

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/124591585