设计模式——行为型之命令模式(Command Pattern)让你的命令发起者和命令执行者的逻辑解耦(六)

引言

在现实世界中绝大多数都是能简则简,简单的逻辑就可以实现的,要是采用复杂不必要的逻辑往往十分不合理,而在编程的世界中,往往结合具体情况多走些“弯路”反而才是明智之选,所谓前人栽树后人乘凉,通常一个程序系统可能都会经历迭代更新开发,有时候为了让后人更好的乘凉(tiankeng),牺牲一些逻辑是很有必要的(根据具体情况判断是否需要)。附行为型设计模式系列文章列表:

一、命令模式概述

命令模式(Command Pattern)是一个高内聚的行为型模式,将请求封装为对象,使用不同请求参数化客户端,排队或记录请求,并支持可撤销操作。(Encapsulate a request as an object,therebyletting you parameterize clients with different requests,queue or log requests,and support undoable operations.)其本质是对命令的抽象和封装,把通常的命令发起——>接收到命令并执行过程,通过增加一个中间角色变为命令发起——>中介负责先接收命令再转发——>接收到命令再执行过程,简而言之,就是命令发起者可以将很多命令放进中间角色, 它并不知道功能是如何实现的,只负责转发给真正的命令执行者。主要涉及到三大角色——命令接收者ReceiverCommand命令角色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();
    }
}

这个变形模式引自《设计模式之禅》

猜你喜欢

转载自blog.csdn.net/crazymo_/article/details/79268658