一、作用

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,而不需要去改变原始的类或者接口。

二、适用场景

1. 业务的接口与工作的类不兼容,(比如:类中缺少实现接口的某些方法)但又需要两者一起工作

2. 在现有接口和类的基础上为新的业务需求提供接口

三、常见的使用方式

还是以Usb接口和Phone手机类的产品举例子,假设设计的Phone类中有 call(), sms(), takeAlong()属性方法,而在设计Usb接口时定义了 store(), takeAlong()的行为。如果现在有新的业务需求,需要生成 Xiaomi手机类具有 Phone类和Usb接口两者功能,假设Phone类和Usb接口已经在业务上投入使用,很显然,去修改原类中的方法和接口的行为去满足现在的新业务需求是不可取的,那么现在适配者模式就派上用场了。

适配器模式主要有两种:类适配模式和对象适配模式

1.类适配模式

大致的意思是新的业务类Xiaomi通过继承旧业务的类Phone并实现接口Usb来满足新的业务的一种适配方式。

代码如下:

Usb接口类


public  interface Usb{

    public void store();
    public void takeAlong();
}

Phone 类


public class Phone {

    public void call(){
        System.out.print("phone can call");
    }

    public void sms(){
        System.out.print("phone can send messages");
    }

    public void takeAlong() {
        System.out.println("Phone takeAlong");
    }
}

适配后的Xiaomi类


public class Xiaomi extends Phone implements Usb{

    public void store() {
        System.out.println("store implements usb");
    }

}

适配完成后 就可以使用xiaomi 类去完成一些功能


Xiaomi xm = new Xiaomi();
xm.takeAlong();
xm.store();
xm.call();

2.对象适配模式

实现的方式很简单,其实就是在适配的时候通过构造函数将旧的业务Phone 当作新的适配类(XiaomiWrapper)一个成员对象去处理,然后适配类只需要实现接口 Usb即可。


public class XiaomiWrapper  implements Usb{
    
    /**
     * 1.创建一个Wrapper类,持有原类的一个实例, 
     * 2.在Wrapper类的方法中,调用实例的方法就行 
     */
    private Phone phone;

    public XiaomiWrapper(Phone phone) {

        this.phone = phone;
    }
    @Override
    public void store() {
        
    }

    @Override
    public void takeAlong() {
        phone.takeAlong();
    }
}

适配完后通过构造函数将原对象传入即可。


XiaomiWrapper xiaomiWrapper = new XiaomiWrapper(new Phone());
xiaomiWrapper.store();
xiaomiWrapper.takeAlong();

Android 源码中的适配器模式的很好运用就是listView 中使用Adapter来显示数据。



// 代码省略
 ListView myListView = (ListView)findViewById(listview_id);
 // 设置适配器
 myListView.setAdapter(new MyAdapter(context, myDatas));
 
适配器类

// 适配器
public class MyAdapter extends BaseAdapter{

        private LayoutInflater mInflater;
        List<String> mDatas ; 

        public MyAdapter(Context context, List<String> 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模式 ( 不止Adapter模式 )来运作的呢 ?

ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。



public abstract class AbsListView extends AdapterView<ListAdapter> 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 < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }

        if (mFastScroller != null && mItemCount != mOldItemCount) {
            mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
        }
        // 布局Child View
        layoutChildren();
        mInLayout = false;

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    }

    // 获取一个Item View
    View obtainView(int position, boolean[] isScrap) {
        isScrap[0] = false;
        View scrapView;
        // 从缓存的Item View中获取,ListView的复用机制就在这里
        scrapView = mRecycler.getScrapView(position);

        View child;
        if (scrapView != null) {
            // 代码省略
            child = mAdapter.getView(position, scrapView, this);
            // 代码省略
        } else {
            child = mAdapter.getView(position, null, this);
            // 代码省略
        }

        return child;
    }
    }

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

ListView中的相关方法。



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 < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++;
        }

        return selectedView;
    }

    // 添加Item View
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;

        // 代码省略 
        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

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


适配器模式的优点


*    更好的复用性


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

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

适配器模式的缺点

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