Android开发经验谈

24 Java设计模式系列-模板方法模式

2021-01-21  本文已影响0人  凤邪摩羯

模板方法模式

模板方法模式是非常常见的设计模式之一,写个笔记,记录一下我的学习过程和心得。

首先了解一些模板方法模式的定义。

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

看定义,也是有点难理解的,但是结合我们的开发,就比较好理解了,因为每个写过代码的人都应该或多或少的用过。

结合我们平时的一些设计,一般会涉及到对某一行为或者做某一件事情的抽象及对其的实现:

抽象父类(AbstractClass):实现了模板方法,定义了算法的骨架。
具体子类(ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。

image

来一个简单的例子吧,程序猿小波觉得搞开发写代码太苦逼而且找不到女票,而且考虑到之前无意间得到一本武林秘籍《thinking in 糕点》,也尝试做过几个美味可口的蛋糕。于是,他决定离职自己创业开了个蛋糕店,然后再招个漂亮的女服务员(关键是要单身,然后内部消化),这样就OK了。
小波经过日夜修炼秘籍,掌握各式糕点的做法,并且融汇贯通,还总结了一套做各式蛋糕的流程:

  1. 材料,各式蛋糕需要各种不同的配料;
  2. 器具,各式蛋糕需要各种不同的器具;
  3. 时间,各式蛋糕成型的时间不一。
public abstract class AbstractCake {
    /**
     * 具体的整个过程
     */
    public void doCake(){
        this.doWhat();
        this.burden();
        this.appliance();
        this.time();
    }

    /**
     * 选择做什么蛋糕
     */
    protected abstract void doWhat();

    /**
     * 备什么材料
     */
    protected abstract void burden();

    /**
     * 选用什么器具
     */
    protected abstract void appliance();

    /**
     * 做多长时间
     */
    protected abstract void time();
}

店铺马上要开张了,并且镁铝服务员也招到了,小波也做过市场调研,马上开始了做最受欢迎的几款蛋糕如下:

草莓奶油蛋糕-StrawberryCake

public class StrawberryCake extends AbstractCake {

    @Override
    protected void doWhat() {
        System.out.println("草莓奶油蛋糕");
    }

    @Override
    protected void burden() {
        System.out.println("我去买草莓和奶油");
    }

    @Override
    protected void appliance() {
        System.out.println("只需要烘焙机");
    }

    @Override
    protected void time() {
        System.out.println("烘焙十五分钟搞定");
    }
}

栗蓉暗香-ChestnutCake


public class ChestnutCake extends AbstractCake {

    @Override
    protected void doWhat() {
        System.out.println("栗蓉暗香蛋糕");
    }

    @Override
    protected void burden() {
        System.out.println("我去买栗子");
    }

    @Override
    protected void appliance() {
        System.out.println("需要十字小道、电饼铛和烘焙机");
    }

    @Override
    protected void time() {
        System.out.println("烘焙十六分钟搞定");
    }
}

榴莲飘飘-DurianCake


public class DurianCake extends AbstractCake {

    @Override
    protected void doWhat() {
        System.out.println("榴莲飘飘蛋糕");
    }

    @Override
    protected void burden() {
        System.out.println("我去买榴莲");
    }

    @Override
    protected void appliance() {
        System.out.println("需要刀、烘焙机");
    }

    @Override
    protected void time() {
        System.out.println("烘焙十二分钟搞定");
    }
}

开始做蛋糕


public class App { 
    public static void main(String[] args) {
        AbstractCake strawberry = new StrawberryCake();
        strawberry.doCake();

        System.out.println("-----------------------------");

        AbstractCake chestnut = new ChestnutCake();
        chestnut.doCake();

        System.out.println("-----------------------------");

        AbstractCake durian = new DurianCake();
        durian.doCake();

    }
}

输出如下:

草莓奶油蛋糕
我去买草莓和奶油
只需要烘焙机
烘焙十五分钟搞定
-----------------------------
栗蓉暗香蛋糕
我去买草莓和奶油
需要十字小道、电饼铛和烘焙机
烘焙十六分钟搞定
-----------------------------
榴莲飘飘蛋糕
我去买榴莲
需要刀、烘焙机
烘焙十二分钟搞定

这样我们就实现了使用模板模式的一个完整的实例。

广泛应用

在Android的源码中,模板模式的使用可以说无处不在

1 系统启动过程
2.组件生命周期,比如Activity和Service等
3.一些具体封装类,比如AsyncTask等

Activity和Service是每个接触过Android学习的人都会非常的了解,这里不做详细的分析。我们主要分析一下常用的AsyncTask。
首先众所周知,AsyncTask的模板就是那几个抽象方法,等你去实现,我们每次使用这个类,就是实现一个具体的子类,而且一个对象只能用一次。

1.onPreExecute() 
2.doInBackground() 
3.onProgressUpdate() 
4.onPostExecute()

抽象父类的细节
首先介绍几个角色:
1.ThreadPool:AsyncTask默认用线程池来切换线程,这个线程池在不同Android版本是不一样的,最初始串行的,后来是并行,现在又是串行,是一个全局的线程池。
2.Handler:这个用于线程的消息交互,主要是子线程通知到UI线程,因为onPostEx’ecute()是在UI线程,所以我们handler必须在U线程初始化。

为什么AsyncTask必须在UI线程初始化呢?
就是以为内部的Handler必须是绑定UI线程的,而这个handler绑定的线程是也是在AsyncTask初始化的当前线程。

3 介绍模板调用细节

1.首先执行AsyncTask.Execute(Params)
这个方法会调用ExecuteOnExecutor(Executor,Params),会传入默认的线程池:sDefaultExecutor和参数

2.ExecuteOnExecutor(Executor,Params)
这个方法会做以下几件事:

1.开始判断AsyncTask对象是否处于Running和Finish状态,如果是就会弹出异常。然后设定这个AsyncTask对象处于running状态。这也是为什么一个AsyncTask对象只能用一次。
2.调用onPreExecute()做一些初始化和准备。
3.调用doInBackground,初始化mWorker,并把Params赋值给mWorker的mParams成员。mWorker实现了Callable接口并在视线中调用了postResult(doInBackground(Params)),这个方法是关键,完成了线程切换,我们后面展开讲。
4.初始化mFuture,这是一个FutureTask对象,可以理解为一个Thread对象。
5.sDefaultExecutor.execute(mFuture),mFuture的run()方法会调用mWorker的call回调方法,最终调用postResult(doInBackground(Params))

  1. postResult(doInBackground(Params))方法
    以上是onPreExecute()和doInBackground()方法已经被调用了
    postResult(doInBackground(Params))这个方法做了一下几个事。

1.构造一个AsyncTaskResult,这个对象是两个对象构造:
AsyncTask对象实例
doInBackground执行完的Result
2.构造一个UI线程Handler的Message,what是MESSAGE_POST_RESULT,obj是AsyncTaskResult
3.发送给UI线程Handler处理。这个handler能处理两类消息就是:
MESSAGE_POST_RESULT:收到这个消息后切换到UI线程,取出obj里面的AsyncTask对象实例调用finish()方法,finish方法会调用>onPostExecute()方法,并且把状态标为finish。
MESSAGE_POST_PROGRESS消息:
会调用AsyncTask对象实例的onUpdateProgress()方法

以上就是AsyncTask的模板流程,我们只需要实现流程中一些对我们开放的特有的细节(就是前文提到的几个接口)就OK,无法影响整个流程的结构。

总结

其实,模板模式说得通俗点一点就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。
接下来,总结一下模板模式的优缺点

优点

  1. 具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
  2. 代码复用的基本技术,在数据库设计中尤为重要。
  3. 存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。

缺点

每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。

适用场景

  1. 在多个子类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中。
  2. 程序主框架相同,细节不同的情况下,也可以使用模板方法。
上一篇下一篇

猜你喜欢

热点阅读