美文网首页
设计模式学习-适配器模式

设计模式学习-适配器模式

作者: m1Ku | 来源:发表于2018-12-02 10:37 被阅读0次

定义

适配器设计模式就是将一个类的接口变换成客户端所期待的另一种接口,从而使原来因接口不兼容而无法一起工作的两个类能一起工作。

适配器模式中有三个角色,分别为目标角色(Target),需要适配的角色(adaptee),和适配器角色(adapter)。

分类

适配器模式分为类适配器模式和对象适配器模式

类适配器模式

UML类图

类适配器模式

我们的目标接口需要Operation2()函数,而Adaptee对象只有一个Operation3()函数。此时,通过让Adapter实现Operation2()函数将Operation3()转换为Target所需要的Operation2(),以此来实现兼容。

示例

以日常生活中的电压为例,我们手机充电需要5V的电压,而供电局提供的是220V电压,此时就出现了接口兼容问题,我们要将220V电压转换为5V。

/**
 * Target目标接口
 * 以电压为例,这里我们需要5V的电压
 */
public interface Volt5 {
    int getVolt5();
}
/**
 * Adaptee需要适配的接口
 * 即这里提供的电压为220v,与我们目标接口的需求不一致,需要适配
 */
public class Volt220 {
    public int getVolt220(){
        return 220;
    }
}
/**
 * 对象适配器模式
 * 通过继承需要适配的类,并实现目标接口
 * 达到关联的效果,实现了两者的兼容
 */
public class VoltAdapter extends Volt220 implements Volt5 {
    @Override
    public int getVolt5() {
        return 5;
    }
}


对象适配器模式

UML类图

对象适配器模式.png

对象适配器模式与类适配器模式不同的是,他使用一个包装类Adapter,在其构造方法中传入adaptee对象,使其API和Target类的API产生关联,这样便达到了适配的目的。

示例

/**
 * 对象适配器模式
 * 通过在适配器的构造方法中传入需要适配的类,
 * 将其方法委托给适配器,使其与目标接口产生关联
 */
public class VoltAdapter implements Volt5 {

    private Volt220 volt220;
    public VoltAdapter(Volt220 volt220){
        this.volt220 = volt220;
    }

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

 public static void main(String[]args){
        VoltAdapter adapter = new VoltAdapter(new Volt220());
        System.out.println(adapter.getVolt5());
    }

对象适配器模式直接将要适配的对象传入到Adapter中,相对于类适配器模式更灵活,而且也不会暴露被适配对象的方法。

Android源码中的适配器模式

​ ListView是我们开发中常用的列表控件,根据不同的业务场景,其数据源以及itemView的布局是千变万化的。ListView通过Adapter将itemView和数据源解耦,达到了适配的目的。下面看一下每一个itemView是如何添加到ListView中的。

首先看一下Adapter的源码

/**
 * 
 * 适配器对象充当了AdapterView和它源数据之间的桥梁的作用。适配器提供获取数据的接口。适配器对象
 * 也负责为数据集中的每一项创建对应的view。
 */
public interface Adapter {
    
    void registerDataSetObserver(DataSetObserver observer);

    void unregisterDataSetObserver(DataSetObserver observer);

    /**
     * 数据集的数量
     */
    int getCount();   
    
    /**
     * 获取数据集中指定位置的数据项
     */
    Object getItem(int position);
   
    long getItemId(int position);
   
    boolean hasStableIds();
    
    /**
     * 获取显示数据集中指定位置数据的view。可以手动创建或者从布局文件inflate出来
     * 
     * @param position adapter中数据集中item的对应view的位置
     * @param convertView 可以重用的view,我们需要检查这个view是否为空。 
     * @param parent 这个view最重要附着到的ViewGroup
     * @return 返回数据集对应位置的view
     */
    View getView(int position, View convertView, ViewGroup parent);
 //...

}

我们发现关于itemView的接口都在适配器的基类中有定义,其中就包括创建itemView的关键方法getView,而这个方法在ListView的适配器中没有实现,这个需要我们用户自己实现。将这里逻辑实现好后,我们将adapter传给ListView,而ListView就负责itemView的添加和布局。接下来看一下ListView的父类AdapterView的onLayout具体的布局方法

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
     /**
     * 子类不应该实现这个方法,而是应该实现layoutChildren()方法 
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //...
        layoutChildren();
        //...
    }
}

具体的布局方法交给了子类ListView在layoutChildren()中实现

public class ListView extends AbsListView {
    
    @Override
    protected void layoutChildren() {
          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:
                final int selectedPosition = reconcileSelectedPosition();
                sel = fillSpecific(selectedPosition, mSpecificTop);
                if (sel == null && mFocusSelector != null) {
                    final Runnable focusRunnable = mFocusSelector
                            .setupFocusIfValid(selectedPosition);
                    if (focusRunnable != null) {
                        post(focusRunnable);
                    }
                }
                break;
            case LAYOUT_MOVE_SELECTION:
                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                break;
            default:
                if (childCount == 0) {
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        sel = fillFromTop(childrenTop);
                    } else {
                        final int position = lookForSelectablePosition(mItemCount - 1, false);
                        setSelectedPositionInt(position);
                        sel = fillUp(mItemCount - 1, childrenBottom);
                    }
                } else {
                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }
                break;
            }
    }
}

在这个方法中会根据不同的布局模式mLayoutMode进行布局,mLayoutMode默认值为LAYOUT_NORMAL,走default分支,而刚开始ListView没有子view,这里走childCount == 0的逻辑,mStackFromBottom是ListView从底部还是顶部开始填充view的标识,其默认值为false。所以,我们看一下fillFromTop方法

/**
 * 从上向下填充list
 */
private View fillFromTop(int nextTop) {
    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    if (mFirstPosition < 0) {
        mFirstPosition = 0;
    }
    return fillDown(mFirstPosition, nextTop);
}

这里调用fillDown方法

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++;
    }
    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

我们注意到view是通过makeAndAddView方法创建的

/**
 *  获取view并将其添加到list中。这个view可以是新创建的,从未使用view转换的,或者是使用缓存中的。
 */
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    if (!mDataChanged) {
            // 尝试获取一个已经存在的view
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                // Found it. We're reusing an existing child, so it just needs
                // to be positioned like a scrap view.
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
    //创建新的view
    final View child = obtainView(position, mIsScrap);
    // 测量并摆放view
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

这个方法中首先调用mRecycler.getActiveView获取一个view,这个方法在AbsListView内部类的RecycleBin中定义的,下面是getActiveView方法数据源mActiveViews的定义

/**
 * Views that were on screen at the start of layout. This array is populated at the start of
 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
 * Views in mActiveViews represent a contiguous range of Views, with position of the first
 * view store in mFirstActivePosition.
 */
private View[] mActiveViews = new View[0];

注释中写着:在开始布局时在屏幕上的view。这个数组的内容是在开始布局时添加的,在布局结束时mActiveViews会转变为mScrapViews。在开始时界面是没有view的,所以这里获取的view是空的。继续通过obtainView方法获取view

View obtainView(int position, boolean[] outMetadata。 {
    //...
    final View child = mAdapter.getView(position, scrapView, this);
    //...
    return child;
}

最后我们发现在layoutChildren()中布局添加的view就是在setAdapter时传进来的adapter的getView方法获得的View。

​ 这样通过添加一层Adapter将itemView的操作抽象出来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。

相关文章

网友评论

      本文标题:设计模式学习-适配器模式

      本文链接:https://www.haomeiwen.com/subject/eifjcqtx.html