java/android 设计模式学习笔记(6)---适配器模式

  这篇来介绍一下适配器模式(Adapter Pattern),适配器模式在开发中使用的频率也是很高的,像 ListView 和 RecyclerView 的 Adapter 等都是使用的适配器模式。在我们的实际生活中也有很多类似于适配器的例子,比如香港的插座和大陆的插座就是两种格式的,为了能够成功适配,一般会在中间加上一个电源适配器,形如:
  这里写图片描述
这样就能够将原来不符合的现有系统和目标系统通过适配器成功连接。
  说到底,适配器模式是将原来不兼容的两个类融合在一起,它有点类似于粘合剂,将不同的东西通过一种转换使得它们能够协作起来。碰到要在两个完全没有关系的类之间进行交互,第一个解决方案是修改各自类的接口,但是如果无法修改源代码或者其他原因导致无法更改接口,此时怎么办?这种情况我们往往会使用一个 Adapter ,在这两个接口之间创建一个粘合剂接口,将原本无法协作的类进行兼容,而且不用修改原来两个模块的代码,符合开闭原则。
  PS:对技术感兴趣的同鞋加群544645972一起交流

设计模式总目录

  java/android 设计模式学习笔记目录

特点

  适配器模式把一个类的接口换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
  所以,这个模式可以通过创建适配器进行接口转换,让不兼容的接口兼容,这可以让客户实现解耦。如果在一段时间之后,我们想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。
  适配器模式的使用场景可以有以下几种:

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

UML类图

  适配器模式在实际使用过程中有“两种”方式:对象适配器和类适配器。

类适配器模式

  首先看一下类适配器模式的 uml 类图:
  这里写图片描述
类适配器是通过实现 ITarget 接口以及继承 Adaptee 类来实现接口转换,目标接口需要的是 operation1() 的操作,而 Adaptee 类只能提供一个 operation2() 的操作,因此就出现了不兼容的情况,此时通过 Adapter 实现一个 operation1() 函数将 Adaptee 的 operation2() 转换为 ITarget 需要的操作,以此实现兼容。类适配器模式有三个角色:

  • Target:目标角色,也就是所期待得到的接口,由于这里讨论的是类适配器模式,因此目标不可以是类;
  • Adaptee:现在需要适配的接口;
  • Adapter:适配器角色,适配器把源接口转换成目标接口,所以这一个角色必须是具体类。

对象适配器模式

  对象适配器模式 uml 类图:
  这里写图片描述
uml 类图和类适配器模式基本一样,区别就在于对象适配器模式与 Adaptee 的关系是 Dependency,而类适配器是 Generalization ,一个是依赖,一个是继承。所以 Adapter 类会持有一个 Adaptee 对象的引用,并且通过 operation1() 方法将该 Adaptee 对象与 ITarget 接口的相关操作衔接起来。
  这种实现方式直接将要被适配的对象传递到 Adapter 中,使用组合的形式实现接口兼容的效果,这种模式比类适配器模式更加灵活,它的另一个好处是被适配对象中的方法不会暴露出来,而类适配器由于继承了被适配对象,因此,被适配对象类的函数在 Adapter 类中也都含有,这使得 Adapter 类出现了一些奇怪的接口,用于使用成本较高。因此,对象适配器模式更加灵活和实用。

对比

  类适配器模式使用的是继承的方式,而对象适配器模式则使用的是组合的方法。从设计模式的角度来说,对象适配器模式遵循 OO 设计原则的“多用组合,少用继承”,这是一个优点,但是类适配器模式有一个好处是它不需要重新实现整个被适配者的行为,毕竟类适配器模式使用的是继承的方式,当然这么做的坏处就是失去了使用组合的弹性。
  所以在实际过程中需要根据使用情况而定,如果 Adaptee 类的行为很复杂,但是 Adapter 适配器类并不需要这些大部分的无关行为,那么使用对象适配器模式是合适的,但是如果需要重新实现大部分 Adaptee 的行为,那么就要考虑是否使用类适配器模式了。

示例与源码

类适配器模式

  我们以最上面说到的香港的英式三角插座和大陆的三角插座为例,来构造类适配器模式,首先是两个插座格式类:
IChinaOutlet.class

public interface IChinaOutlet {
    public String getChinaType();
}

ChinaOutlet.class

public class ChinaOutlet implements IChinaOutlet{
    @Override
    public String getChinaType() {
        return "Chinese three - pin socket";
    }
}

上面是中式插座的输出格式,然后是香港的英式插座输出格式:
HKOutlet.class

public class HKOutlet {
    public String getHKType() {
        return "British three - pin socket";
    }
}

为了将香港的英式插座转换为中式插座,我们需要构造一个 Adapter 类,目的是进行插座格式的转换:
OutletAdapter.class

public class OutletAdapter extends HKOutlet implements IChinaOutlet{
    @Override
    public String getChinaType() {
        String type = getHKType();
        type = type.replace("Chinese", "British");
        return type;
    }
}

这样就实现了插座接口的转换,例子很简单,明了。当然这个例子很简单,要的就是要学会这个思想:在不修改原来类的基础上,将原来类进行扩展后使用在新的目标系统上。

对象适配器模式

  对象适配器模式就以我几年前写过的一个 View 作为例子:android一个转盘效果的容器viewgroup,这个例子就是典型的“需要统一的输出接口,而输入端的类型不可预知”情形,需要输出的是一个个 View ,而输入的数据是未知的。原先的处理方式是使用动态 addChild 的方式添加子 View,然后使用removeChild 方法删除子 View :

...
public void addChild(final View view) throws NumberOverFlowException{
    if(childNum < maxNum){
        //每次添加子view的时候都要重新计算location数组
        location.add(new FloatWithFlag());
        TurnPlateViewUtil.getLocationByNum(location);
        view.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                listener.onClick((String)arg0.getTag());
            }
        });
        view.setOnLongClickListener(new OnLongClickListener() {

            @Override
            public boolean onLongClick(View arg0) {
                initPopUpWindow();
                window.showAsDropDown(arg0);
                viewIsBeingLongClick = arg0;
                return false;
            }
        });
        addView(view);
        childNum++;
    }else{
        throw new NumberOverFlowException(maxNum);
    }
}

public void removeChild(final View view){
    try{
        this.removeView(view);
        location.remove(0);
        childNum--;
        TurnPlateViewUtil.getLocationByNum(location);
        requestLayout();
    }catch(Exception e){

    }
}
...

使用这种方式会造成外部对子 View 的操纵很繁琐,换位思考一下,如果 ListView 需要以 addView 和 removeView 的方式去处理,那是极其头疼的,所以现在我们可以换一种思维进行改进,学习 ListView 的 Adapter 思想,我们也使用适配器的方式进行处理,为了方便这里就直接继承 BaseAdapter 吧,改造后的代码如下:

/**
 * 设置适配器
 * @param adapter
 */
public void setAdapter(BaseAdapter adapter) throws NumberOverFlowException {
    this.adapter = adapter;
    if (adapter.getCount() > MAX_NUM) {
        throw new NumberOverFlowException(adapter.getCount());
    }
    adapter.registerDataSetObserver(new DataSetObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            onDataSetChanged();
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            onDataSetChanged();
        }
    });
    initChild();
}

/**
 * 数据源发生变更,需要重新绘制布局
 */
private void onDataSetChanged(){
    initChild();
}
...
private void initChild() {
    removeAllViews();
    location.clear();

    for (int i=0; i < adapter.getCount(); i++) {
        //每次添加子view的时候都要重新计算location数组
        location.add(new FloatWithFlag());
        TurnPlateViewUtil.getLocationByNum(location);
        View view = adapter.getView(i, null, this);
        view.setTag(i);
        view.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                listener.onClick((String)arg0.getTag());
            }
        });
        view.setOnLongClickListener(new OnLongClickListener() {

            @Override
            public boolean onLongClick(View arg0) {
                initPopUpWindow();
                window.showAsDropDown(arg0);
                viewIsBeingLongClick = arg0;
                return false;
            }
        });
        addView(view);
    }
}

外部使用时直接继承 BaseAdapter 类,然后在对应方法中返回对应 View 即可,这样就实现了“不同的输入,同样的输出”:

private class TurnPlateViewAdapter extends BaseAdapter{

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
        drawable.setBounds(0, 0,drawable.getMinimumHeight() , drawable.getMinimumHeight());
        TextView textview = new TextView(MainActivity.this);
        textview.setTextColor(getResources().getColor(android.R.color.white));
        textview.setText(R.string.text);
        textview.setCompoundDrawables(null, drawable, null, null);
        textview.setTag(tag++ +"");
        return textview;
    }
}

这样,外部修改输入数据之后,通知 adapter 数据源变更,因为已经注册观察者,所以 TurnplateView 自然而然可以收到通知,并且刷新界面,最后实现效果和以前一样:
这里写图片描述
  由此感慨,在最初学习 android 的时候,listView 的 adapter 知道怎么使用,但是并没有去深究为什么这么使用,其实里面很多地方都透着设计模式的思想,源码真可谓是第一手学习资料。

总结

  Adapter 模式的经典实现在于将原本不兼容的接口融合在一起,使之能够很好的进行合作。但是,在实际开发中, Adapter 模式也会可以根据实际情况进行适当的变更,最典型的就是 ListView 和 RecyclerView 了,这种设计方式使得整个 UI 架构变得非常灵活,能够拥抱变化。所以在实际使用的时候,遵循上面说过的三种场景:

  1. 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容;
  2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大联系的一些类,包括一些可能在将来引进的类一起工作;
  3. 需要一个统一的输出接口,而输入端的类型不可预知。
根据情况进行变化,将适配器模式灵活运用在实际开发中。
  总结下来,Adapter 模式的优点基本已经明确了:
  • 更好的复用性
  • 系统需要使用现有的类,而此类的接口不符合系统的需要,那么通过适配器模式就可以让这些功能得到更好的复用;
  • 更好的扩展性
  • 在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
总结一下就是对扩展开放和对修改关闭的开闭原则吧。
  当然适配器模式也有一些缺点,如果在一个系统中过多的使用适配器模式,会让系统非常零乱,不易整体把握。例如,明明看到调用的是 A 接口,其实内部被适配成 B 类的实现,这样就增加了维护性,过多的使用就显得很没有必要了,不如直接对系统进行重构。

源码下载

  https://github.com/zhaozepeng/Design-Patterns/tree/master/AdapterPattern

引用

http://www.android100.org/html/201506/20/155883.html
https://en.wikipedia.org/wiki/Adapter_pattern

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
        我在前一篇博客中《 iOS开发实战——CollectionView点击事件与键盘隐藏结合案例 》详细实现了CollectionView与键盘组合操作中出现的多种情况,并解决了交互体验上的一些问题。在实际项目中也的确可以采用这种方法来操作。但是问题来了,原来的界面我们是使用UIView来操作的,也就是界面是不可滚动的。然而更为常见的场景是一个ScrollView,界面可以进行上下滚动。所以,这篇博客主要是对前一个案例进行优化。还有一个问题是,在自动布局Masonry结合ScrollView中
内存相关的问题在面试中被问到的概率还是比较大的,而且内存优化对于一个程序的性能而言也是至关重要的,现在就让我们一起来学习吧! 不废话,直接上干货~ 一、内存泄漏 内存泄漏就是我们对某一内存空间的使用完成后没有释放。 主要原因:导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象。 出现的场景: 1.数据库的cursor没有关闭; 2.构造adapter时,没有使用缓存contentview; 3.Bitmap对象不使用时采用recycle()释放
Google在Android 5.X 中增加了对SVG 矢量图形的支持,这对于创建新的高效率动画具有非常重大的意义。那首先了解SVG的含义。 可伸缩矢量图形(Scalable Vector Graphics) 定义用于网络的基于矢量的图形 使用XML格式定义图形 图像在放大或改变尺寸的情况下其图形质量不会有所损失 万维网联盟的标准 与诸如DOM和XSL之类的W3C标准是一个整体 SVG在Web上的应用非常广泛,在Android 5.X之前的Android版本上,可以通过一些第三方开源库来在Android中
已经封装好, 点我下载、好用就Star一下 Thanks 创建继承于UIView的视图 .h文件 // backGoundView @property ( nonatomic , strong ) UIView * _Nonnull backGoundView; // titles @property ( nonatomic , strong ) NSArray * _Nonnull dataArray; // images @property ( nonatomic , strong ) NSArray
转载请标明出处: http://blog.csdn.net/Airsaid/article/details/51591282 本文出自: 周游的博客 前言 上一篇写了补间动画的使用,由于篇幅原因,就把自定义补间动画单独拿出来了。这一篇继续写补间动画~ 在上一篇中写到了Android提供了Animation类作为补间动画的抽象基类,并提供了四个子类:ScaleAnimation 、TranslateAnimation、AlphaAnimation、RotateAnimation分别实现了四种基本动画形式:缩
转载请标明出处: 一片枫叶的专栏 去年一整年android社区中刮过了一阵热修复的风,各大厂商,逼格大牛纷纷开源了热修复框架,恩,产品过程中怎么可能没有bug呢?重新打包上线?成本太高用户体验也不好,咋办?上热修复呗。 好吧,既然要开始上热修复的功能,那么就得调研一下热修复的原理。下面我将分别讲述一下热修复的原理,各大热修复框架的比较,以及自身产品中热修复功能的实践。 热修复的原理 通过更改dex加载顺序实现热修复 最新github上开源了很多热补丁动态修复框架,大致有: HotFix        Nu
要用到高德地图的SDK,首先要获取sha1,获取Android studio下的sha1的方法可以切到.android下,输入命令: keytool -list -keystore debug.keystore 。但是会出现如下错误; 分析原因,原理是没有正确配置java环境变量,正确配置环境变量的方法可以参考如下链接: http://jingyan.baidu.com/article/c85b7a6414f2ee003bac95d5.html 这时可以在Android studio的Terminal中输
今天我们开始进入讲解android中的一些高级主题的用法,比如传感器、GPS、NFC、语音和人脸识别等。 这次来对传感器的一个简单介绍: Android平台支持三大类的传感器: 位移传感器 这些传感器测量沿三个轴线测量加速度和旋转。这类包含加速度,重力传感器,陀螺仪,和矢量传感器。 环境传感器 这些传感器测量各种环境参数,例如周围的空气温度和压力,光线,和湿度。这类包含气压,光线,和温度传感器。 位置传感器 这些传感器测量设备的物理位置。这类包含方向和磁力传感器。 这些传感器的一些是基于硬件的,一些是基于
OpenglES2.0 for Android:来画个立方体吧 前言: 前面一直在说OpenglES2.0二维图形的绘制,接下来我们步入三维的世界 ,三维世界远比二维要有趣的多,与此同时复杂性也要高得多,在unity3D中我们可以很容易的就创建 一个立方体,而在OpenglES2.0中这个过程要复杂得多,但是更加有趣 。先来看下我们的整个流程: 摄像机的设置: 想想你的摄像头,它的位置不同,朝向不同,对同一个事物拍摄得到的画面肯定是不同的,Opengl中的摄像头和我们日常生活中的摄像头是一样的道理 (图一
在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用 StrongReference 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止