关于设计模式:设计模式学习笔记十七命令模式

37次阅读

共计 6483 个字符,预计需要花费 17 分钟才能阅读完成。

1 概述

1.1 引言

日常生活中,能够通过开关管制一些电器的开启和敞开,比方电灯和排气扇。能够将开关了解成一个申请发送者,电灯是申请的最红接收者以及解决者,开关与电灯之间不存在间接的耦合关系,两者通过电线连贯在一起,使不同的电线能够连贯不同的申请接收者,只须要更换一根电线,雷同的发送者(开关)既可对应不同的接收者(电器)。

软件开发中常常须要向某些对象发送申请,然而并不知道具体的接收者是谁,也不晓得被申请的操作是哪个,此时心愿以一种松耦合的形式来设计软件,使得申请发送者与申请接收者之间可能打消彼此之间的耦合,让对象之间的调用关系更加灵便,能够灵便地指定申请接收者以及被申请的操作,此时能够应用命令模式进行设计。

命令模式能够将申请发送者和接收者齐全解耦,发送者与接收者之间没有间接援用关系,发送申请的对象只须要晓得如何发送申请,而不用晓得如何实现申请。

1.2 定义

命令模式:将一个申请封装成一个对象,从而可用不同的申请对客户进行参数化,对申请排队或者记录申请日志,以及反对可撤销的操作。

命令模式是一种对象行为型模式,别名为动作模式或者事务模式。

1.3 结构图

1.4 角色

  • Command(形象命令类):形象命令类个别是一个抽象类或者接口,在其中申明了用于执行申请的 execute() 办法,通过这些办法能够调用申请接收者的相干操作
  • ConcreteCommand(具体命令类):实现了形象命令类中申明的办法,对应具体的接收者对象,将接收者对象的动作绑定其中,在实现 execute() 办法时,将调用接收者对象的相干操作
  • Invoker(调用者):调用者即申请发送者,通过命令对象来执行申请。一个调用者并不需要设计时确定接收者,因而它只与形象命令类之间存在关联关系。程序运行时将具体命令对象注入,并调用其中的 execute() 办法,从而实现间接调用申请接收者的相干操作
  • Receiver(接收者):接收者执行与申请相干的操作,具体实现对申请的业务解决

2 典型实现

2.1 步骤

  • 定义形象命令类:定义执行申请的办法
  • 定义调用者:在调用办法外面蕴含对具体命令的调用,同时须要蕴含一个对形象命令的援用
  • 定义接收者:定义接管申请的业务办法
  • 定义具体命令类:继承 / 实现形象命令类,实现其中执行申请办法,转发到接收者的接管办法

2.2 形象命令类

这里实现为一个接口:

interface Command
{void execute();
}

2.3 调用者

class Invoker
{
    private Command command;

    public Invoker(Command command)
    {this.command = command;}

    public void call()
    {System.out.println("调用者操作");
        command.execute();}
}

调用者能够通过构造方法或者 setter 注入具体命令,对外提供一个调用办法call,当调用此办法时调用具体命令的execute

2.4 接收者

class Receiver
{public void action()
    {System.out.println("接收者操作");
    }
}

这里的接收者只有一个action,示意接管办法。

2.5 具体命令类

class ConcreteCommand implements Command
{private Receiver receiver = new Receiver();
    @Override
    public void execute()
    {receiver.action();
    }
}

具体命令类中须要蕴含一个对接收者的援用,以便在 execute 中调用接收者。

2.6 客户端

public static void main(String[] args) 
{Invoker invoker = new Invoker(new ConcreteCommand());
    invoker.call();}

通过构造方法注入具体命令到调用者中,接着间接调用即可。

输入如下:

3 实例

自定义功能键的设置,对于一个按钮,能够依据须要由用户设置为最小化 / 最大化 / 敞开性能,应用命令模式进行设计。

设计如下:

  • 形象命令类:Command
  • 调用者:Button
  • 接收者:MinimizeHandler+MaximizeHandler+CloseHandler
  • 具体命令类:MinimizeCommand+MaximizeCommand+CloseCommand

首先设计形象命令类,实现为一个接口,仅蕴含 execute 办法:

interface Command
{void execute();
}

接着是调用者类,蕴含一个形象命令的援用:

class Button
{
    private Command command;
    public Button(Command command)
    {this.command = command;}

    public void onClick()
    {System.out.println("按钮被点击");
        command.execute();}
}

而后是接收者类:

class MinimizeHandler
{public void handle()
    {System.out.println("最小化");
    }
}

class MaximizeHandler
{public void handle()
    {System.out.println("最大化");
    }
}

class CloseHandler
{public void handle()
    {System.out.println("敞开");
    }
}

最初是具体命令类,对应蕴含一个接收者成员即可,实现其中的 execute 并转发到接收者的办法:

class MinimizeCommand implements Command
{private MinimizeHandler handler = new MinimizeHandler();
    @Override
    public void execute()
    {handler.handle();
    }
}

class MaximizeCommand implements Command
{private MaximizeHandler handler = new MaximizeHandler();
    @Override
    public void execute()
    {handler.handle();
    }
}

class CloseCommand implements Command
{private CloseHandler handler = new CloseHandler();
    @Override
    public void execute()
    {handler.handle();
    }
}

测试类:

public static void main(String[] args) 
{Button button = new Button(new MinimizeCommand());
    button.onClick();

    button = new Button(new MaximizeCommand());
    button.onClick();

    button = new Button(new CloseCommand());
    button.onClick();}

输入:

如果须要新增一个命令,只须要命令接收者以及实现了 Command 的具体命令类,客户端再将具体命令注入申请发送者(Button),毋庸间接操作申请接收者。

4 命令队列

有时候须要将多个申请排队,当一个申请发送者发送实现一个申请后,不止一个申请接收者产生响应,这些申请接收者将一一执行业务办法实现对申请的解决。这种模式能够通过命令队列实现,实现命令队列很简略,个别是减少一个叫 CommandQueue 的类,由该类负责存储多个命令对象,不同的命令对象能够对应不同的申请接收者,比方在下面的例子中减少 CommandQueue 命令队列类:

class CommandQueue
{private ArrayList<Command> commands = new ArrayList<>();
    public void add(Command command)
    {commands.add(command);
    }

    public void remove(Command command)
    {commands.remove(command);
    }

    public void execute()
    {System.out.println("批量执行命令");
        commands.forEach(Command::execute);
    }
}

接着批改调用者类 Button(只需将原来的Command 改为CommandQueue):

class Button
{
    private CommandQueue queue;
    public Button(CommandQueue queue)
    {this.queue = queue;}

    public void onClick()
    {System.out.println("按钮被点击");
        queue.execute();}
}

最初是客户端定义命令队列并作为参数传入调用者的构造方法或者 setter 中,最初由调用者执行办法:

public static void main(String[] args) 
{CommandQueue queue = new CommandQueue();
    queue.add(new MinimizeCommand());
    queue.add(new MaximizeCommand());
    queue.add(new CloseCommand());
    Button button = new Button(queue);
    button.onClick();}

输入如下:

5 撤销与重做

设计一个繁难计算器,实现加法性能,还可能实现撤销以及重做性能,应用命令模式实现。

设计如下:

  • 形象命令类:Command
  • 调用者:Calculator
  • 接收者:Adder
  • 具体命令类:AddCommand

首先先不实现撤销以及重做性能:

public class Test
{public static void main(String[] args) 
    {Calculator calculator = new Calculator(new AddCommand());
        calculator.add(3);
        calculator.add(9);
    }
}

interface Command
{int execute(int value);
}

class Calculator
{
    private Command command;
    public Calculator(Command command)
    {this.command = command;}

    public void add(int value)
    {System.out.println(command.execute(value));
    }
}

class Adder
{
    private int num = 0;
    public int add(int value)
    {return num += value;}
}

class AddCommand implements Command
{private Adder adder = new Adder();
    @Override
    public int execute(int value)
    {return adder.add(value);
    }
}

代码与下面的实例相似,就不解释了。

这里要害的问题是如何实现撤销以及重做性能,撤销可能复原到进行加法之前的状态,而重做能复原到进行了加法之后的状态,而且这是有固定程序的,因而能够联想到数组,应用下标示意以后状态,下标左移示意撤销,下标右移示意重做:

应用一个状态数组存储每次进行加法的状态,用下标示意以后状态,当撤销时,使下标左移,当重做时,使下标右移。

首先须要批改形象命令类,增加撤销以及重做办法:

interface Command
{int execute(int value);
    int undo();
    int redo();}

接着批改调用者类,增加撤销以及重做办法:

class Calculator
{
    private Command command;
    public Calculator(Command command)
    {this.command = command;}

    public void add(int value)
    {System.out.println(command.execute(value));
    }

    public void undo()
    {System.out.println(command.undo());
    }

    public void redo()
    {System.out.println(command.redo());
    }
}

外围的实现位于接收者类 Adder,应用了List<Integer> 存储了状态,index示意下标,在撤销或重做之前首先判断下标地位是否非法,非法则进行下一步操作:

class Adder
{private List<Integer> nums = new ArrayList<>();
    private int index = 0;
    public Adder()
    {nums.add(0);
    }

    public int add(int value)
    {int result = nums.get(index)+value;
        nums.add(result);
        ++index;
        return result;
    }

    public int redo()
    {if(index + 1 < nums.size())
            return nums.get(++index);
        return nums.get(index);
    }

    public int undo()
    {if(index - 1 >= 0)
            return nums.get(--index);
        return nums.get(index);
    }
}

最初具体命令类简略增加撤销以及重做办法即可:

class AddCommand implements Command
{private Adder adder = new Adder();
    @Override
    public int execute(int value)
    {return adder.add(value);
    }

    @Override
    public int undo()
    {return adder.undo();
    }

    @Override
    public int redo()
    {return adder.redo();
    }
}

测试:

public static void main(String[] args) 
{Calculator calculator = new Calculator(new AddCommand());
    calculator.add(3);
    calculator.add(9);
    
    calculator.undo();
    calculator.undo();
    calculator.undo();
    calculator.undo();
    
    calculator.redo();
    calculator.redo();
    calculator.redo();
    calculator.redo();}

6 次要长处

  • 升高耦合度:因为请求者与接收者之间不存在间接援用,因而请求者与接收者之间实现齐全解耦,雷同的申请能够对应不同的接收者,同样雷同的接收者也能够供不同的请求者应用,两者之间具备良好的独立性
  • 满足 OCP:新的命令能够很容易增加到零碎中,因为减少新的具体命令类不会影响到其余类,因而减少新的具体命令类很容易,满足 OCP 的要求
  • 撤销 + 中作:为申请的撤销以及重做提供了一种设计和实现计划

7 次要毛病

  • 过多具体命令类:应用命令模式可能会导致系统有过多的具体命令类,因为针对每一个申请接收者的调用操作都须要设计一个具体工具类,因而在某些零碎中可能须要提供大量的具体命令类

8 实用场景

  • 零碎须要将申请调用者和申请接受者解耦,使得调用者和接收者不间接交互,申请调用者毋庸晓得接收者的存在,也无需晓得接收者是谁,接收者也毋庸关怀何时被调用
  • 零碎须要在不同工夫指定申请,将申请排队和执行申请
  • 零碎须要反对撤销以及复原操作
  • 零碎须要将一组操作组合一起造成宏命令,应用命令队列实现

9 总结

如果感觉文章难看,欢送点赞。

同时欢送关注微信公众号:氷泠之路。

正文完
 0