引言
在现实世界中绝大多数都是能简则简,简单的逻辑就可以实现的,要是采用复杂不必要的逻辑往往十分不合理,而在编程的世界中,往往结合具体情况多走些“弯路”反而才是明智之选,所谓前人栽树后人乘凉,通常一个程序系统可能都会经历迭代更新开发,有时候为了让后人更好的乘凉(tiankeng),牺牲一些逻辑是很有必要的(根据具体情况判断是否需要)。附行为型设计模式系列文章列表:
- 设计模式——行为型之使用模板方法(Template Method Pattern)模式尽量减少重复相似的代码段(一)
- 设计模式——行为型模式之通过中介者模式(Mediator Pattern)实现各模块之间的解耦(二)
- 设计模式——行为型模式之借助策略模式(Strategy Pattern)减少使用不必要的if-else if -else和switch-case(三)
- 设计模式——行为型设计模之借助观察者模式(Observer Pattern)实现模块之间的解耦(四)
- 设计模式——行为型模式之借助责任链模式(Chain of Responsibility)灵活完成链式处理(五)
- 设计模式——行为型之命令模式(Command Pattern)让你的命令发起者和命令执行者的逻辑解耦(六)
- 设计模式——行为型之使用备忘录模式(Memento Pattern)随时回滚状态(七)
一、命令模式概述
命令模式(Command Pattern)是一个高内聚的行为型模式,将请求封装为对象,使用不同请求参数化客户端,排队或记录请求,并支持可撤销操作。(Encapsulate a request as an object,therebyletting you parameterize clients with different requests,queue or log requests,and support undoable operations.)其本质是对命令的抽象和封装,把通常的命令发起——>接收到命令并执行过程,通过增加一个中间角色变为命令发起——>中介负责先接收命令再转发——>接收到命令再执行过程,简而言之,就是命令发起者可以将很多命令放进中间角色, 它并不知道功能是如何实现的,只负责转发给真正的命令执行者。主要涉及到三大角色——命令接收者Receiver、Command命令角色和Involker调用者角色:
命令接收者Receiver——所谓命令的接收者,应该是命令的最终接收者,是命令真正被执行的地方,所有转发的命令,最终都是由它去执行的,通常是一个具体的命令实施实体对象。
Involker调用者角色——而Involker只是个负责转发命令请求的中间角色,用于暂时存储命令请求的中转站,调用对应的Command角色实例来转发。
Command命令角色——通常分为两种角色:抽象命令角色和具体的Command角色,通常会定义一个所有执行命令的入口方法,在接收者和命令执行的具体行为之间加以弱耦合。
二、命令模式的优点和缺点及可用场景
1、命令模式的优点
在命令模式中, 请求者不直接与接收者交互, 在代码中体现为请求者不需持有接收者的引用, 从而降低了耦合度。
命令模式符合开闭原则(Open Close Principle),提高了代码的灵活性和可维护性。如果增加新的具体命令和该命令的接收者, 不必修改调用者的代码, 调用者就可以使用新的命令对象; 反之, 如果增加新的调用者, 不必修改现有的具体命令和接收者, 新增加的调用者就可以使用自己已有的具体命令
由于请求者被封装到了具体命令中, 那么就可以将具体命令保存到持久化的媒介中, 命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
使用命令模式可以对请求者的”请求”进行排队. 每个请求都各自对应一个具体命令,因此可以按照一定的顺序执行这些命令.
命令的添加特别方便,并且可以方便的制定各种命令和利用现有命令组合出新的命令。
扫描二维码关注公众号,回复: 1708086 查看本文章命令模式结合其他模式会更优秀,命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
2、命令模式的缺点
命令模式也是有缺点的,由于把命令抽象,那么通常有多少种命令就应该有多少个子类,会导致Command类很膨胀,另外由于需要把命令发起者和命令执行者解耦,使得程序之间原本简单就可以实现的逻辑,变得必须用复杂逻辑去完成,不过很多时候设计模式都是需要牺牲一点逻辑的。
3、命令模式的可用场景及注意事项
需要抽象出待执行的动作时,可以以参数来封装不同的命令,有点类似于面向过程中的回调机制,而命令模式是回调机制的一个面向对象的替代品
在不同时刻指定、排列和执行不同的动作集时,并且需要记录日志和回滚时也可考虑。
三、命令模式的实现
以控制一个机器人运动和说话为例
1、首先定义Receiver角色
//最终接收命令的角色,相当于是命令的真正执行者,是真正处理相关命令对应的逻辑实现,其他类或角色都是间接使用这个类
public class CrazyRobot {
public void move(int type) {
switch (type) {
case 0:
System.out.println("向上移动中...");
break;
case 1:
System.out.println("向下移动中...");
break;
case 2:
System.out.println("向左移动中...");
break;
case 3:
System.out.println("向右移动中...");
break;
default:
break;
}
}
public void talk(){
System.out.println("Hello CrazyBot...");
}
}
2、定义抽象命令接口角色
//把所有的命令都抽象为一个统一的命令接口,即命令角色抽象(也可以定义为抽象类。)
public interface ICommand {
public void excute();
}
3、实现具体的实体命令角色
我这里把具体命令分为两大类:运动类和说话类
package commandpattern;
//具体命令角色,可以根据命令的类型来对应实现运动命令类角色
public class MoveCommandImpl implements ICommand {
private CrazyRobot robot;
private int type;
public MoveCommandImpl(CrazyRobot bot,int type){
this.robot=bot;
this.type=type;
}
@Override
public void excute() {
robot.move(type);
}
public void setType(int type){
this.type=type;
}
}
package commandpattern;
public class TalkCommandImpl implements ICommand {
private CrazyRobot robot;
public TalkCommandImpl(CrazyRobot robot) {
this.robot = robot;
}
@Override
public void excute() {
robot.talk();
}
}
4、实现Involker角色
这里有两种形式:一种是分别持有所有具体命令类的引用
package commandpattern;
public class HandleControler {
private MoveCommandImpl moveUp;
private MoveCommandImpl moveDown;
private MoveCommandImpl moveLeft;
private MoveCommandImpl moveRight;
private TalkCommandImpl talkCommandImpl;
public void setMoveUp(MoveCommandImpl moveUp) {
this.moveUp = moveUp;
}
public void setMoveDown(MoveCommandImpl moveDown) {
this.moveDown = moveDown;
}
public void setMoveLeft(MoveCommandImpl moveLeft) {
this.moveLeft = moveLeft;
}
public void setMoveRight(MoveCommandImpl moveRight) {
this.moveRight = moveRight;
}
public void setTalkCommandImpl(TalkCommandImpl talkCommandImpl) {
this.talkCommandImpl = talkCommandImpl;
}
public void moveUp(){
moveUp.excute();
}
public void moveDown(){
moveDown.excute();
}
public void moveLeft(){
moveLeft.excute();
}
public void moveRight(){
moveRight.excute();
}
public void talk(){
talkCommandImpl.excute();
}
}
另一种是把所有命令类都存到集合中
package commandpattern;
import java.util.ArrayList;
public class InvokerHandle {
private ArrayList<ICommand> commands;
public InvokerHandle() {
commands = new ArrayList<>();
}
public void setCommand(int i, ICommand command) {
commands.add(i, command);
}
public void update(int i) {
commands.get(i).excute();
}
}
6、客户端调用
package commandpattern;
public class Player {
public static void main(String[] args) {
//构造真正的命令最终执行者
CrazyRobot robot=new CrazyRobot();
//构造真正的命令实体
MoveCommandImpl moveUp=new MoveCommandImpl(robot,0);
MoveCommandImpl moveDown=new MoveCommandImpl(robot,1);
MoveCommandImpl moveLeft=new MoveCommandImpl(robot,2);
MoveCommandImpl moveRight=new MoveCommandImpl(robot,3);
TalkCommandImpl talkCommandImpl=new TalkCommandImpl(robot);
HandleControler controler=new HandleControler();
controler.setTalkCommandImpl(talkCommandImpl);
controler.setMoveUp(moveUp);
controler.setMoveDown(moveDown);
controler.setMoveLeft(moveLeft);
controler.setMoveRight(moveRight);
//由玩家随意发出那种指令
controler.talk();
controler.moveUp();
controler.moveDown();
controler.moveRight();
controler.moveUp();
controler.moveLeft();
System.out.println("------------");
InvokerHandle invoker = new InvokerHandle();
invoker.setCommand(0, talkCommandImpl);
invoker.setCommand(1, moveUp);
invoker.setCommand(2, moveDown);
invoker.setCommand(3, moveLeft);
invoker.setCommand(4, moveRight);
invoker.update(3);
}
}
四、变形的命令模式
1、抽象并实现Recevier角色
public abstract class Receiver {
//抽象接收者,定义每个接收者都必须完成的业务
public abstract void doSomething();
}
//具体的Receiver类
public class ConcreteReciver1 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
public class ConcreteReciver2 extends Receiver{
//每个接收者都必须处理一定的业务逻辑
public void doSomething(){
}
}
2、定义抽象Command角色
public abstract class Command {
//定义一个子类的全局共享变量
protected final Receiver receiver;
//实现类必须定义一个接收者
public Command(Receiver _receiver){
this.receiver = _receiver;
}
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
public class ConcreteCommand1 extends Command {
//对哪个Receiver类进行命令处理
private Receiver receiver;
//构造函数传递接收者
public ConcreteCommand1(Receiver _receiver){
this.receiver = _receiver;
}
//必须实现一个命令
public void execute() {
//业务处理
this.receiver.doSomething();
}
}
public class ConcreteCommand2 extends Command {
//声明自己的默认接收者
public ConcreteCommand2(){
super(new ConcreteReciver2());
}
//设置新的接收者
public ConcreteCommand2(Receiver _receiver){
super(_receiver);
}
//每个具体的命令都必须实现一个命令
public void execute() {
//业务处理
super.receiver.doSomething();
}
}
3、定义Involker角色
public class Invoker {
private Command command;
//受气包,接受命令
public void setCommand(Command _command){
this.command = _command;
}
//执行命令
public void action(){
this.command.execute();
}
}
4、客户端使用
public class Client {
public static void main(String[] args) {
//首先声明调用者Invoker
Invoker invoker = new Invoker();
//定义接收者
Receiver receiver = new ConcreteReciver1();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand1(receiver);
//把命令交给调用者去执行
invoker.setCommand(command);
invoker.action();
}
}
这个变形模式引自《设计模式之禅》