Android中的设计模式-命令模式

两位大侠均系出名门,适配器大侠身为结构教长老,而策略大侠位居行为派护法。二侠虽然门派不同,素昧平生,却也一见如故,把酒言欢之余,心意相通,准备合奏一曲助兴。只见适配器大侠使出乾坤大挪移,无论何种乐器,或吹、或拉、或弹、或敲,不管音调如何,或轻、或重、或缓、或急,都被他一一化解为一种和音,无不处处落在策略大侠的旋律上。丝丝入扣,不差毫厘,冥冥之中自有天意,“适配器”大侠与“策略”大侠竟然合成了一首《命令模式曲》。曲终之后,二位大侠自是喜不待言,在场的各大门派的编程名家、架构大师更是赞叹不已,在击节叫好之余,都不由地暗下决心:是时候修炼《命令模式72绝技》了!于是大家纷纷打开真经,首先映入眼帘的就是它的定义和结构图。

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

基本结构图:
这里写图片描述
该图中包括如下一些基本的类:

  • Command(命令接口类):定义命令的接口,声明执行操作的方法。
  • ConcreteCommand(具体的命令类):定义具体命令的类。通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • Invoker(命令调用者):使用命令的类,该类的职责是请求某个命令对象执行待定命令,通常会持有命令,真正使用命令功能的地方。
  • Receiver(命令接收者):命令接收者,知道在接收到一个命令后,如何完成具体的处理操作,是真正执行命令功能的地方。任何一个类都可以成为命令接受者。
  • Client:创建具体的命令对象,设置命令的接收者,并把命令对象传递给命令调用者,它通过Invoker来触发命令的执行。

它是典型的行为型模式,任何一个对象中的任何一个方法(在这里是请求),可以说是林林总总,什么形式的方法都有,要让Invoker使用固定的形式来调用一个对象中的方法,就得找到变化并封装之,在这里,方法是变化的根源,那就用Command类来封装这个方法。

总之,Client把接收者的方法创建成一个命令对象,交给调用者,调用者调用命令来让接收者处理操作。从结构图中可以看出Invoker和Command是策略模式,在这里Invoker是Context角色,而Command是策略对象,显然Invoker和Command解耦了。那么,具体Command对象委托引用Receiver的操作方法,也被封装在了具体Command中,Invoker与Command解耦也就是Invoker与Receiver解耦,无论是处理什么样的操作,调用者都不会修改,操作与调用者解耦了。也就是说,在实际业务中Receiver提供的操作方法尽管接口形式各异,没法提供一个共同的抽象基类来实现面向接口编程(不同的Receiver提供的方法没有共同点,不可能把它们都继承在同一个基类中,因为对Receiver没有规定,任何一个类都可以用作Receiver,显然它们之间是不可能抽象出基类的),那就使用Command这个中间类来过渡:需要操作Receiver中的一个方法,就创建一个新的Command类来对应,来封装它(封装变化)。与策略模式相比,比策略模式多了一层适配包装:Command作为适配器来适配Receiver与Invoker。因此,为了便于这个模式的理解,可以把它看作是策略模式+适配器模式的组团应用,核心思想就是把一个对象的方法封装成对象。

该模式的特点:

  1. Receiver集中精力做好自己的功能,可以根据自己的意愿随意定义方法,其它模块使用它时,按照它的方法来调用。
  2. Invoker的对外调用的接口是Command,Command是一个通用的接口,面向接口编程;Command通过适配器来转换Receiver的接口方法为对象,封装变化。
  3. Receiver的变化只影响到Command的变化,而Command对Invoker而言,又是面向接口的,对Invoker没有任何影响,也即Invoker通过Command接口封装了Receiver的变化!
  4. 当Receiver扩展功能时,新建一个新Command子类即可,Invoker没有任何变化,符合对扩展开放,对修改关闭的原则。
  5. Command把调用者(Invoker)和接收者(Receiver)隔离开了,解耦了调用者和接收者,尽管接收者形形色色,对调用者没有任何影响。

显然命令模式也可以代替回调函数,实现IOC机制,比如Android中的Handler机制。

好,我们先看一下在开发实践中是如何使用线程的:假设MyBusiness是我们的一个业务类,有一个doSomthing()方法用来处理业务逻辑,如果想要doSomthing()异步运行,通过线程来实现,非常简单,代码片段如下:

public void client() {
        MyBusiness business = new MyBusines();// 创建Receiver对象
        Runnable concreteRunnable = new Runnable() {// 创建具体的Command对象

            publicvoid run() {
                business.doSomthing();// 在具体命令中封装Receiver的操作
            }
        };

        Thread thread = new Thread(concreteRunnable);// 创建Invoker对象
        thread.start();// 向调用者Invoker发送命令请求
    }

有没有发现这就是命令模式的典型用法?Thread类就是Invoker,Runnable接口就是抽象Command类,ConcreteRunnable就是具体Command对象,而MyBusiness就是Receiver类,它里面的方法doSomthing()就是Invoker最终调用的操作。尽管MyBusiness形式各异,里面的方法也千变万化,但是它的任何方法,比如doSomthing()、doAnything()、doEverything()…都可以放在Thread中异步运行,并且Thread还不会做任何修改,怎么做到的呢?就是通过Runnable这个Command抽象接口(适配器)给封装了变化,解耦了Thread和MyBusiness,命令模式居然如此简单。我们在使用时有没有意识到这就是命令模式得场景?

再者,在Android应用开发中,也有一个常见的命令模式应用场景:使用Handler向一个HandlerThread去发送一个Message或者Runnable对象。创建和发送命令的client角色是就是使用Handler的应用程序,包含Looper对象的线程就是命令的调用者Invoker角色,Message或Runnable就是命令Command角色,而Handler就是命令的接收者Receiver角色。如果要Undo一个命令,可以调用removeCallbacks()、removeMessages()、removeCallbacksAndMessages()等方法,经典的命令模式应用场景,大家可以分析一下。

使用该模式需要注意的地方:

  1. 既然把对象的操作方法封装成了对象,那么扩展一个新操作就得定义一个对应的ConcreteCommand类来封装它,这会引起ConcreteCommand类的数量增多,线程中常见的方式是匿名类,也是新增的类。
  2. 向Invoker请求一个操作时,就得创建一个Command对象实例,调用一次就创建一个,如果请求的次数比较多,就会产生大量的一次性对象。因此,为了防止内存的剧烈抖动,可以使用对象池来复用命令对象。如在Android应用中,当向Handler发送Message对象时,可以使用Message.obtain(),从Message对象池中获取一个闲置Message对象,当使用完毕后,再把Message对象归还到Message对象池中。
  3. Invoker调用Receiver的操作,可以使用同步方式,也可以使用异步方式,完全由Invoker根据自己的业务逻辑决定,在Handler与线程的例子中,都是异步方式的例子。
  4. 由于命令Command可以储存在Invoker中,如果长时间不处理的话,它就迟迟得不到释放,而Command对象又委托引用了一个Receiver,这样Receiver对象也迟迟得不到释放,如果Receiver是重量级对象的话,对内存的占用开销也很大。比如,在Android中,如果Handler是Activity中的一个内部类的实现对象,在它的内部类中重写了handleMesage(),那么它就隐含引用了Activity对象,这样即使Activity失去生命周期了,也无法被释放回收,这在使用这种模式应用时需要注意的一点。

实现一个文件回收站的例子:当删除文件时,可以把文件存放在回收站中,这样可以在后来把文件从中恢复回来。借助于这个回收站,我们可以实现一个文件操作的undo功能,如,可以对文件的copy、create、delete、move等操作进行undo操作,既然命令模式有撤销操作的功能,就使用命令模式实现再合适不过了。
假设回收站的目录为:“/recycler/”,代码如下:

public class CommandPattern {
    private final static String RECYCLER = "/recycler";

    // 命令接口类
    interface ICommand {
        void execute();
    }

    // 命令调用者,负责保存命令,并执行命令
    static class Invoker {
        private Stack<ICommand> mStack; // undo命令要放在堆栈中,按照先进后出的顺序undo

        public Invoker() {
            mStack = new Stack();
        }

        public void backupCommand(ICommand command) { //保存Undo命令
            mStack.push(command);
        }

        public void undo() { //执行undo操作
            try {
                ICommand command = mStack.pop();
                command.execute();
            } catch (EmptyStackException e) {
                e.printStackTrace();
            }
        }
    }

    // 文件操作接口类
    interface IFileOperation {
        void create(String file);
        void copy(String src, String dst);
        void move(String src, String dst);
        void delete(String file);
        void undo();
    }

    // 一个文件操作具体类
    static class FileOperation implements IFileOperation {
        public void create(String file) {
            System.out.println("create:"+file);
        }

        public void copy(String src, String dst) {
            System.out.println("copy:"+src+" to "+dst);
        }

        public void move(String src, String dst) {
            System.out.println("move:"+src+" to "+dst);
        }

        public void delete(String file) {
            System.out.println("delete:"+file);
        }

        public void undo() {
        }
    }

    // 文件操作的代理类,代理模式
    static class FileOperationProxy extends FileOperation {
        private FileOperation mOperation;
        private Invoker mInvoker;

        public FileOperationProxy(FileOperation operation) {
            mOperation = operation;
            mInvoker = new Invoker();
        }

        @Override
        public void create(final String newFile) {
            mOperation.create(newFile);
            mInvoker.backupCommand(new ICommand() {
                @Override
                public void execute() {
                    mOperation.delete(newFile); // undo操作相当于删除文件
                }
            });
        }

        @Override
        public void copy(final String file1, final String file2) {
            mOperation.copy(file1, file2);
            mInvoker.backupCommand(new ICommand() {
                @Override
                public void execute() {
                    mOperation.delete(file2); // undo操作相当于删除目的文件
                }
            });
        }

        @Override
        public void delete(final String newFile) {
            mOperation.move(newFile, RECYCLER+newFile);//删除操作相当于移动到回收站
            mInvoker.backupCommand(new ICommand() {
                @Override
                public void execute() {
                    mOperation.move(RECYCLER+newFile, newFile);// undo操作相当于从回收站移动回来
                }
            });
        }

        @Override
        public void move(final String file1, final String file2) {
            mOperation.move(file1, file2);
            mInvoker.backupCommand(new ICommand() {
                @Override
                public void execute() {
                    mOperation.move(file2, file1); //undo操作相当于反方向移动
                }
            });
        }

        @Override
        public void undo() {
            mInvoker.undo();
        }
    }

    // 模拟一个文件操作场景
    public static void testUndo(boolean undoFlag) {
        final String newFile = "/abc/120.txt";
        final String file1 = "/abc/121.txt";
        final String file2 = "/abc/122.txt";
        final String file3 = "/abc/123.txt";
        final String file4 = "/abc/124.txt";

        // 如果不需要undo功能,直接使用FileOperation,否使用FileOperationProxy
        FileOperation operation = new FileOperation();
        if (undoFlag) { // undo设置标记打开
            operation = new FileOperationProxy(operation);
        }
        operation.create(newFile);
        operation.copy(file1, file2);
        operation.move(file3, file4);
        operation.delete(file2);

        operation.undo();
        operation.undo();
        operation.undo();
        operation.undo();
    }

    // 测试
    public static void main(String[] argv) {
//        testUndo(false);
        testUndo(true);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值