关闭

Android源码中的命令模式

标签: 命令模式设计模式Android源码
2717人阅读 评论(0) 收藏 举报
分类:

写在前面

从装饰者模式到Context类族

当观察者模式和回调机制遇上Android源码

Android源码中的静态工厂方法

Android中的工厂方法模式

前面跟大家分享了装饰者模式、观察者模式、静态工厂方法、工厂方法模式,今天跟大家分享下Android源码中的命令模式。

命令模式

定义

将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

使用场景

对于大多数请求——响应模式的功能,比较适合使用命令模式。

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队(如:线程池+工作队列)和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作(比如系统挂掉之后重启做一些恢复操作,还有数据库的事务等)。
  • 系统需要将一组操作组合在一起,即支持宏命令。

结构

命令模式涉及到的角色:

  • 客户角色(Client):Client可以创建具体的命令对象,并且设置命令对象的接收者。Tips:不能把Clinet理解为我们平常说的客户端,这里的Client是一个组装命令对象和接受者对象的角色,或者你把它理解为一个装配者。
  • 调用者角色(Invoker):负责调用命令对象执行请求,通常会持有命令对象(可以持有多个命令对象)。Invoker是Client真正触发命令并要求命令执行相应操作的地方(使用命令对象的入口)。
  • 命令角色(Command):定义命令的接口,声明具体命令类需要执行的方法。这是一个抽象角色。
  • 具体命令角色(ConcreteCommand):命令接口的具体实现对象,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 接收者角色(Receiver):Receiver是真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

实现

命令模式其实就是对命令进行封装,将命令请求者和命令执行者的责任分离开来实现松耦合。 这里我们通过一个餐厅点餐的实例来剖析一下命令模式:命令接收者Cook可以做各式各样的菜,根据Waiter送过来的订单来满足顾客的需求,具体命令实现类PigCook执行做烤乳猪命令,DuckCook执行烧花鸭命令等等,Client负责组装各个部分。

命令角色

public interface Command {
    public void execute();
    public void undo();
    public void redo();
}

命令接收者

public class Cook {

    //烤乳猪的方法
    public void cookPig(){

    }

    //烧花鸭的方法
    public void cookDuck(){

    }
}

具体命令角色

//做烤乳猪的命令
public class PigCook implements Command {

    private Cook mCook;

    public PigCook(Cook cook) {
        mCook = cook;
    }

    @Override
    public void execute() {
        mCook.cookPig();
    }

    @Override
    public void undo() {

    }

    @Override
    public void redo() {

    }
}
//做烧花鸭的命令
public class DuckCook implements Command {

    private Cook mCook;

    public DuckCook(Cook cook) {
        mCook = cook;
    }

    @Override
    public void execute() {
        mCook.cookDuck();
    }

    @Override
    public void undo() {

    }

    @Override
    public void redo() {

    }
}

调用者角色

public class Waiter {

    private Command pig;
    private Command duck;

    public void setCommandPig(Command pig) {
        this.pig = pig;
    }
    public void setCommandDuck(Command duck) {
        this.duck = duck;
    }

    /**
     * 执行正常命令,这里省略了undo和redo操作
     */
    public void invoke(int args) {
        //可以根据具体情况选择执行某些命令
        if(args == 0){
            pig.execute();
        }else if(args == 1){
            duck.execute();
        }
    }
}

客户角色

public class Client {
    /**
     * 组装操作
     */
    public void assembleAction() {

        //创建一个命令接收者
        Cook mCook = new Cook();

        //创建一个命令的具体实现对象,并指定命令接收者
        Command pig = new PigCook(mCook);
        Command duck = new DuckCook(mCook);

        Waiter mWaiter = new Waiter();//创建一个命令调用者
        //为调用者指定烤乳豬命令对象
        mWaiter.setCommandPig(pig);
        //为调用者指定烧花鸭命令对象
        mWaiter.setCommandDuck(duck);

        //发起调用烤乳猪命令请求
        mWaiter.invoke(0);
        //发起调用烧花鸭命令请求
        mWaiter.invoke(1);
    }
}

可是,为什么要这么复杂咧,我只是想点个菜而已嘛,直接这么搞不就好了?

public class Client {
    /**
     * 组装操作
     */
    public void assembleAction() {

        //创建一个命令接收者
        Cook mCook = new Cook();

        //发起调用烤乳猪命令请求
        mCook.cookPig();
        //发起调用烧花鸭命令请求
        mCook.cookDuck();
    }
}

我们知道命令模式的一个优点是支持命令的撤销(Undo)操作和恢复(Redo)操作,如果我们像上边一样调用,我们要想做撤销是不是就不那么方便了呢。同时还可以考虑下命令模式的其他几个优点。

总结

  • 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。

  • 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

  • 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。

  • 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

Android源码中的命令模式

对于Android源码来说,Android底层逻辑对事件的转发处理就用到了命令模式。Application Framework(应用程序框架层)中PackageManagerService类(包管理部分)也用到了命令模式。PackageManagerService是Android系统的Service之一,主要功能是实现对应用包的解析、管理、卸载等操作。我们来看下具体的结构。

HandlerParams是命令接口,即我们的Command角色。

private abstract class HandlerParams {
    private static final int MAX_RETRIES = 4;

    /**
     * Number of times startCopy() has been attempted and had a non-fatal
     * error.
     */
    private int mRetries = 0;

    /** User handle for the user requesting the information or installation. */
    private final UserHandle mUser;
    String traceMethod;
    int traceCookie;

    HandlerParams(UserHandle user) {
        mUser = user;
    }

    UserHandle getUser() {
        return mUser;
    }

    HandlerParams setTraceMethod(String traceMethod) {
        this.traceMethod = traceMethod;
        return this;
    }

    HandlerParams setTraceCookie(int traceCookie) {
        this.traceCookie = traceCookie;
        return this;
    }

    final boolean startCopy() {
        boolean res;
        try {
            if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);

            if (++mRetries > MAX_RETRIES) {
                Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                mHandler.sendEmptyMessage(MCS_GIVE_UP);
                handleServiceError();
                return false;
            } else {
                handleStartCopy();
                res = true;
            }
        } catch (RemoteException e) {
            if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
            mHandler.sendEmptyMessage(MCS_RECONNECT);
            res = false;
        }
        handleReturnCode();
        return res;
    }

    final void serviceError() {
        if (DEBUG_INSTALL) Slog.i(TAG, "serviceError");
        handleServiceError();
        handleReturnCode();
    }

    abstract void handleStartCopy() throws RemoteException;
    abstract void handleServiceError();
    abstract void handleReturnCode();
}

具体的包的安装、移动以及包大小的测量分别在3个具体子类InstallParams、MoveParams和MeasureParams中实现。

而PackageHandler是Handler的子类,用来负责包相关消息的处理,不同的请求对应不同的命令对象,然后通过命令对象来执行具体操作。

关于Receiver

通过接触Android源码或者其他的一些源码,我们知道有些地方是没有命令接收者(Receiver)这个角色的,这是为什么呢?

个人认为,有的命令接收实现非常简,可以直接用少量的代码来实现,没有必要再增加类的数量。

参考链接:

https://github.com/simple-android-framework/android_design_patterns_analysis/tree/master/command/lijunhuayc

http://blog.csdn.net/wwh578867817/article/details/51533263

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:185361次
    • 积分:2159
    • 等级:
    • 排名:第19274名
    • 原创:38篇
    • 转载:0篇
    • 译文:3篇
    • 评论:103条
    我的微信公众号
    长期为您推荐优秀博文、开源项目、视频等,进入还有好玩的等着你。扫一扫下方二维码或搜索微信号codertopia即可关注:
    我的

    QQ群:469291650


    GitHub
    我的个人博客
    博客专栏
    Relax
    最新评论