Android开发之深入理解策略(Strategy)模式

2017-07-06 16:07 阅读 934 次 评论 0 条
版权声明:本文著作权归TeachCourse所有,未经许可禁止转载,谢谢支持!
转载请注明出处:http://teachcourse.cn/2449.html

摘要:

什么样的程序设计被称为策略模式?什么时候使用策略模式?为什么用策略模式代替抽象类或接口?这篇文章引用构建(Builder)模式的例子,将绘制钟表的过程改写成符合策略模式程序设计,程序结构如下图:

策略模式demo结构

  • WatchViewImpl继承View,实现自定义钟表视图
  • WatchViewActivity显示钟表效果
  • IClock定义绘制钟表的接口,impl目录的三个文件分别是接口IClock的三种实现,处理钟表的绘制逻辑,客户可以自由选择使用其中的一种策略。

一、关键代码分析

本程序如果不使用设计模式,只需要WatchViewImplWatchViewActivity即可实现,绘制钟表的逻辑全部写在自定义视图类内,不足的地方:如果想要修改绘制钟表的逻辑,唯一的方式就是修改WatchViewImpl,修改的同时又想保留原来的绘制逻辑,如果将第一个版本的逻辑实现在当前类封装后保留,再添加新的绘制逻辑,自定义视图类显得比较臃肿,不符合程序设计的单一设计原则

这时候开始考虑设计模式——策略(strategy)模式,看一下WatchViewActivity关键代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        cn.teachcourse.strategy.WatchViewImpl watchView=new cn.teachcourse.strategy.WatchViewImpl(this);
        /*第一种策略:默认算法*/
        IClock defaultClock=new DefaultClockImpl(watchView);
        /*第二种策略:常用算法*/
        IClock normalClock=new NormalClockImpl(watchView);
        /*第三种策略:设计算法*/
        IClock designClock = new DesignClockImpl(watchView);

        /*自由选择使用其中的一种策略,比如:normalClock*/
        watchView.setIClock(normalClock);
        setContentView(watchView);
    }

三种算法彼此之间没有影响,如果想要添加第四种算法,不需要修改或者改动很少自定义视图类WatchViewImpl,创建第四个算法类实现接口IClock,处理绘制钟表的逻辑,然后在客户端WatchViewActivity选择实例化的对象即可,符合程序设计中的开闭原则

看一下IClock的关键代码:

/**
 * Created by http://teachcourse.cn on 2017/7/5.
 */

public interface IClock {
    void paint(Canvas canvas);
}

再来看一下默认策略算法DefaultClockImpl的实现逻辑,关键代码:

public DefaultClockImpl(WatchViewImpl watchView) {
        this.watchView = watchView;
    }

    @Override
    public void paint(Canvas canvas) {
        //移动画布中心点到当前View中心
        canvas.translate(getWidth() / 2, getHeight() / 2);
        //绘制外圆背景颜色
        paintExternalCircle(canvas);
        //绘制内圆背景颜色
        paintCircle(canvas);
        //绘制刻度
        paintScale(canvas);
        //绘制指针
        paintPointer(canvas);
    }
    /**
     * 绘制钟表外部圆形背景
     */
    public void paintExternalCircle(Canvas canvas) {
        ...
    }

    /**
     * 绘制钟表的内部圆形背景
     *
     * @param canvas
     */
    public void paintCircle(Canvas canvas) {
        ...
    }

    /**
     * 绘制钟表刻度
     *
     * @param canvas
     */
    private void paintScale(Canvas canvas) {
        ...
    }

    /**
     * 绘制钟表上的时针、分针、秒针
     *
     * @param canvas
     */
    private void paintPointer(Canvas canvas) {
        ...
    }

    ...
}

对比DesignClockImpl设计算法实现的逻辑,关键代码:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            postInvalidate();
        }

    };


    public DesignClockImpl(WatchViewImpl watchView) {
        this.watchView = watchView;
        mPaint=new Paint();
        mPath = new Path();
    }

    @Override
    public void paint(Canvas canvas) {
        ...
    }

    ...
}

罗列关键代码的目的,想让读者可以对策略模式的设计结构一个整体的认识DefaultClockImpl类和DesignClockImpl类分别是两种不同处理绘制钟表的逻辑,两种策略的效果图分别如下:

默认策略模式

设计策略模式,实现效果图:

设计策略模式

小明同学可能喜欢绘制挂在墙上的钟表,类似于DefaultClockImpl效果;小红同学想要绘制带在手上的电子表,比较时尚,类似于DesignClockImpl效果;如果小李还想绘制一款与小红、小明不一样的钟表,那也不是不可能,小李构思一下绘制的逻辑,重新实现IClock接口即可。

二、策略模式分析

在钊林的另一篇介绍UML的文章里说过,学习设计模式,UML图谱可以简单清晰表示类之间的关系,可以整体理解设计模式的思路,不懂UML图谱的同学先了解一下。

绘制上述Demo的UML图谱如下:

策略模式UML

UML类图的含义:WatchViewActivity依赖于WatchViewImpl,同时WatchViewImpl依赖于IClock接口;DefaultClockImplNormalClockImplDesignClockImpl实现接口,为了方便部分同学理解,总结一下UML图的基本语法:

  • 类的UML图分为抽象类非抽象类,抽象类名使用斜体字形;
  • 接口的UML图,接口名使用斜体字形,同时使用《Interface》符号修饰;
  • 泛化关系的UML图使用实线空心箭头表示;
  • 关联关系的UML图使用实线箭头表示;
  • 依赖关系的UML图使用虚线箭头表示;
  • 实现关系的UML图使用虚线空心箭头表示

策略模式:定义了算法族,分别封装起来,让它们之间可以相互代替,此模式让算法的变化独立于使用算法的客户。

通用的UML类图:

策略模式UML类图

学习设计模式的过程,比较简单的方法,记住每种设计模式通用的UML类图,依样画葫芦,创建同样结构的类关系,达到使用设计模式的目的,比如上文绘制钟表的例子,IClock对应StrategyWatchViewImpl对应Context

三、为什么用策略模式代替抽象类或接口?

在绘制钟表的例子中,另外的两种处理方法:

第一种:可以尝试将自定义视图类WatchViewImpl,声明为一个抽象类,声明抽象方法void paint(Canvas canvas),让子类实现绘制逻辑,不同的子类实现不同的效果,这样也是可以实现自定义视图的多样化的。

抽象类的UML类图:

抽象类的UML类图

不足地方:
如果存在另一个WatchView,比如:WatchViewCopy,该类想要使用DefaultWatchViewImpl绘制钟表的逻辑代码,通常的做法复制一份到WatchViewCopy的子类中,结果出现比较多的重复代码;类似的,如果想要使用另外两个子类的绘制逻辑,依然继续复制;

这个时候,策略模式的优势就体现出来了,处理绘制钟表代码的逻辑与WatchViewImpl没有联系,其他需要IClock接口的类,可以简单注入对应的实现类,可以实现代码的复用。

上面的说法如果不容易理解,那么再举一个例子,WatchView作为一个自定义视图类,通常可以在布局文件中使用,当用户选择使用DefaultWatchViewImpl,必然布局文件、Activity类关联的是DefaultWatchViewImpl的引用,如果这时候更换NormalWatchViewImpl,需要改动的地方势必比策略模式要多。

当然,还有别的原因,这里列举的两个不足,想要说明可以使用抽象类的地方可以使用策略模式代替,反过来则不行。

第二种:如果使用接口,定义接口IClock,定义方法void paint(Canvas canvas),让WatchView继承View的同时实现接口方法,处理绘制钟表的逻辑全部写在了自定义视图类内,这个时候的接口相比抽象类,扩展性能更不好,然后就可以因此引生出策略模式啦。

接口的UML类图:

接口的UML类图

四、总结

全文从引入绘制钟表的例子作为入口,让读者对策略模式的设计过程一个整体的认识;第二部分引入策略模式的定义、模板,让读者可以快速掌握该模式的使用;第三部分,从例子的角度出发,说明策略模式比抽象类或接口的优势,目的想要阐明:多用组合,少用继承的观点,对对象的行为可能需要复用的情况,选择使用策略模式代替抽象类,抽象类和接口同样有存在的优势,具体看开发的需要。

关注公众号 扫一扫二维码,加我QQ

如果文章对你有帮助,欢迎点击上方按钮关注作者

来源:TeachCourse每周一次,深入学习Android教程,关注(QQ1589359239或公众号TeachCourse)
转载请注明出处:http://teachcourse.cn/2449.html

资源分享

Demo源码
Genymotion如何添加下载过的设备? Genymotion如何添加下载过的设备
浅谈final关键字 浅谈final关键字
冒泡算法 冒泡算法
Android系统搜索框架实战:提示最近查询内容 Android系统搜索框架实战:提示

发表评论

呲牙 憨笑 坏笑 偷笑 色 微笑 抓狂 睡觉 酷 流汗 鼓掌 大哭 可怜 疑问 晕 惊讶 得意 尴尬 发怒 奋斗 衰 骷髅 啤酒 吃饭 礼物 强 弱 握手 OK NO 勾引 拳头 差劲 爱你

表情