设计模式——模板方法模式

之前公司的apk更新都是一个链接跳转到应用市场,让用户去应用市场下载。但最近产品觉得要减少用户操作的步骤,将下载改为从自己的后台进行下载,直接在应用内更新。而且要求下载时要在界面上弹出一个显示进度的对话框,下载结束后跳转安装apk的界面。

好吧,需求来了咱们就得实现。这个其实不难,就是开个Service,之后在里面创建个子线程做网络文件下载,下载开始发个通知告诉界面弹个对话框。下载中不断发送下载进度更新下载进度的显示。下载完成后关闭对话框,并且启动安装apk的界面。

但是作为一个有追求的码农。我们要想一件事情,怎么架构这个下载操作会好一点。试想一下,这个Service只会用来下载apk么?后面完全可能会有下载文件,下载图片,下载数据库的需求。

我们发现,这些需求的大致流程都是一样的,比如都是下载开始,下载中,下载结果(成功,失败)。只是在部分状态可能有一些差别。比如apk下载的话,下载结束要开启安装apk的界面;下载文件下载结束不做什么操作;下载数据库,为了防止一些三方框架的缓存问题,下载结束我们可能需要重启app……那我们能不能共用这个Service代码,而对每个不同需求的特殊阶段又有特殊的实现呢?

接下来我们引进一下这个开发中非常常用的设计模式——模板方法模式。他不仅常用,在Android中还极其常见。后面我们会介绍Android中模板方法模式的应用,相信你一定会大吃一惊,因为你每天都在跟模板方法模式打交道。

先上模板方法模式的UML静态类图:


模板方法有这样几个主要的角色:

抽象父类:flow方法中封装了一套逻辑流程,这个流程一般是固定的。为了防止子类随意覆盖该方法,一般这个方法都会声明为final的。stepOne,stepTwo等方法是整个流程中的几个步骤,不同的实现可能在这几个步骤上有所差别。

实现子类:SubClass01,SubClass02等等是继承了抽象父类的子类。他们根据自己的特殊需要覆盖相应的步骤方法。

这样,在保证完整流程不变的情况下,对其中的细节步骤做了按需实现。

回到我们开头提到的例子,我们先把需求明确一下。

现在我们需要完成一个下载工具,暂定下载两种:

(1)下载数据库,下载时通过dialog显示进度,下载完成后消除dialog。

(2)下载apk,下载时通过通知栏显示进度,下载完成后消除通知栏。

一般下载我们都是在一个Service中开启一个子线程来进行。方便起见我们可以直接使用IntentService。下载部分我们这里就简单模拟一个下载过程,不去真的下载文件了。

(1)首先我们来完成抽象父类角色:

public abstract class DownloadService extends IntentService {

    public DownloadService() {
        super("DownloadService");
    }

    @Override
    protected final void onHandleIntent(@Nullable Intent intent) {
        //下载开始的回调
        onDownloadStart();
        int progress = 0;
        while (progress < 100) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            progress++;
            //下载中的回调
            onDownloadProgress(progress);
        }
        //下载成功的回调
        onDownloadSuccess();
    }

    protected void onDownloadStart() {
        Log.d("Download", "Abs_DownloadStart");
    }

    protected void onDownloadProgress(int progress) {
        Log.d("Download", "Abs_DownloadProgress:" + progress);
    }

    protected void onDownloadSuccess() {
        Log.d("Download", "Abs_DownloadSuccess");
    }

    protected void onDownloadFail() {
        Log.d("Download", "Abs_DownloadFail");
    }

}

这里我们定义了四个回调方法,即下载开始,下载中进度更新,下载成功,下载失败。这四个步骤其实就是可能会随着需求不同,而导致细节变化的步骤。

onHandlerIntent方法其实就是UML中的flow方法。我们这里设置为final,防止子类随意重写固定的流程。里面我们仅仅是做了个假的更新,并不断更新Progress的值。

(2)实现下载apk的Service

public class DownloadAPKService extends DownloadService {

    private NotificationManager mManager;
    private NotificationCompat.Builder mBuilder;

    private static final int NOTIFICATION_ID = 1001;

    @Override
    public void onCreate() {
        super.onCreate();
        mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }

    @Override
    protected void onDownloadStart() {
        super.onDownloadStart();
            mBuilder = new NotificationCompat.Builder(this);
            mBuilder.setProgress(100, 0, false);
            mBuilder.setSmallIcon(R.mipmap.ic_launcher);
            mBuilder.setContentTitle("下载APK");
            mBuilder.setContentText("正在下载");
            mManager.notify(NOTIFICATION_ID, mBuilder.build());
    }

    @Override
    protected void onDownloadProgress(int progress) {
        super.onDownloadProgress(progress);
        mBuilder.setProgress(100, progress, false);
        mManager.notify(NOTIFICATION_ID, mBuilder.build());
    }

    @Override
    protected void onDownloadSuccess() {
        super.onDownloadSuccess();
        mManager.cancel(NOTIFICATION_ID);
    }

}

这里我们实际根据需要覆写了onDownloadStart方法,onDownloadProgress方法,onDownloadSuccess方法。在onDownloadStart方法中我们开启了一个通知栏。在onDownloadProgress方法中我们更新了通知栏的进度条。在onDownloadSuccess方法中我们关闭了通知栏。这样就实现了我们对于apk下载的需求。

(3)实现下载db的Service

这里为了便于弹出提示框和更新UI,我们还是借助一下EventBus吧。

首先贴一下提示框的布局和代码,这里我们只是简单写一下。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="48dp">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下载db"
        android:textColor="@android:color/black"
        android:textSize="28sp" />

    <TextView
        android:id="@+id/tv_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:textColor="@color/colorPrimary"
        android:textSize="48sp"
        tools:text="80" />

    <TextView
        android:id="@+id/tv_percent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_toRightOf="@+id/tv_progress"
        android:text="%"
        android:textColor="@android:color/black"
        android:textSize="48sp" />

</RelativeLayout>
public class ProgressDialogFragment extends android.support.v4.app.DialogFragment {

    public static final String PROGRESS_DIALOG_FRAGMENT_TAG = "PROGRESS_DIALOG_FRAGMENT_TAG";

    private TextView tv_progress;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    }

    @Nullable
    @Override
    public View onCreateView(
            LayoutInflater inflater,
            @Nullable ViewGroup container,
            Bundle savedInstanceState
    ) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_progress_dialog, null);
        tv_progress = (TextView) view.findViewById(R.id.tv_progress);
        return view;
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onReceivedDownloadDBProgressEvent(DownloadDBProgressEvent downloadDBProgressEvent){
        tv_progress.setText(String.valueOf(downloadDBProgressEvent.getProgress()));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

}

之后我们新建一个DownloadDBService类,继续继承我们的抽象父类DownloadService。

public class DownloadDBService extends DownloadService {

    @Override
    protected void onDownloadStart() {
        super.onDownloadStart();
        EventBus.getDefault().post(
                new DownloadDBStartEvent()
        );
    }

    @Override
    protected void onDownloadProgress(int progress) {
        super.onDownloadProgress(progress);
        EventBus.getDefault().post(
                new DownloadDBProgressEvent(progress)
        );
    }

    @Override
    protected void onDownloadSuccess() {
        super.onDownloadSuccess();
        EventBus.getDefault().post(
                new DownloadDBSuccessEvent()
        );
    }

}

我们覆写了父类的onDownloadStart方法,onDownloadProgress方法和onDownloadSuccess方法。只是简单在这三个方法中用EventBus发送了相应事件,便于更新UI。

之后我们在MainActivity中添加两个方法,当接收到下载开始的事件时开启dialog,当接收到下载结束的事件时关闭dialog。

public class MainActivity extends AppCompatActivity {

    private ProgressDialogFragment mProgressDialogFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EventBus.getDefault().register(this);

        Button btn_download_apk = (Button) findViewById(R.id.btn_download_apk);
        btn_download_apk.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Intent intent = new Intent(MainActivity.this, DownloadAPKService.class);
                        startService(intent);
                    }
                }
        );

        Button btn_download_db = (Button) findViewById(R.id.btn_download_db);
        btn_download_db.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Intent intent = new Intent(MainActivity.this, DownloadDBService.class);
                        startService(intent);
                    }
                }
        );
    }

    @Subscribe
    public void onReceivedDownloadDBStartEvent(DownloadDBStartEvent downloadDBStartEvent){
        mProgressDialogFragment = new ProgressDialogFragment();
        mProgressDialogFragment.show(
                getSupportFragmentManager(),
                ProgressDialogFragment.PROGRESS_DIALOG_FRAGMENT_TAG
        );
    }

    @Subscribe
    public void onReceeivedDownloadDBSuccessEvent(DownloadDBSuccessEvent downloadDBSuccessEvent){
        mProgressDialogFragment.dismiss();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

}

由于例子写的比较简单,我们就不上效果图了。

通过这个例子我们可以看到模板方法模式的好处。首先他保证了主流程不变,并且利用继承提高了代码的复用性。其次,它允许特殊需求的实现。可以看到我们的DownloadAPKService和DownloadDBService对几个步骤细节的实现是完全不一样的。最后,由于模板方法模式,使得DownloadAPKService和DownloadDBService解耦。并且如果后续有其他新的下载需求,再写一个Service即可。

想象一下如果不用模板方法模式的情况吧,一个Service里面根据传入的内容不同做一堆if判断。根据条件执行相应代码。后面只要来一个新需求,这个Service类的代码就得改动。而且随着需求越来越多,这个类会越来越庞大。我们现在知道了模板方法模式,回头看这种实现是不是觉得烂透了?

最后提几个Android中模板方法模式的运用吧。其实相信大家看了上面代码应该有答案。没错,我们整天打交道的Activity的生命周期方法,Fragment的生命周期方法,还有AsyncTask的几个回调方法用的就是模板方法模式。

好了,模板方法模式就介绍到此,希望对各位有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值