设计模式之命令模式

设计模式之命令模式,第1张

设计模式命令模式

说实话这个模式挺令人纠结的,但从这个模式的定义上来看,有点让人摸不到什么头脑,而且查看资料以后会发现还是有点稀里糊涂的,说懂了吧也很简单,也不懂吧也有不懂的理由,于是查阅手头的各种书籍,在此写下心得体会,算是加深一下印象。

命令模式的定义:将请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,以及支持可撤销和恢复的功能。

从定义上来看着实令人一脸懵逼,在这里描述一下博主个人的理解:

请求:客户端要求系统执行的 *** 作,在java的世界里面就是某个对象的方法。

Command:请求封装成的对象,该对象是命令模式的主角。也就是说将请求方法封装成一个命令对象,通过 *** 作命令对象来 *** 作请求方法。在命令模式是有若干个请求的,需要将这些请求封装成一条条命令对象,客户端只需要调用不同的命令就可以达到将请求参数化的目的。将一条条请求封装成一条条命定对象之后,客户端发起的就是一个个命令对象了,而不是原来的请求方法!(行为参数化,在Java8得到了更好的支持)

Receiver:接受者,当然有命令的接收者对象:如果有只有命令,没有接受者,那不就是光棍司令了?没有电视机或者电脑主机,你对着电视机遥控器或者电脑键盘狂按有毛用?Receiver对象的主要作用就是受到命令后执行对应的 *** 作。对于点击遥控器发起的命令来说,电视机就是这个Receiver对象,比如按了待机键,电视机收到命令后就执行了待机 *** 作,进入待机状态。

Client:但是有一个问题摆在眼前,命令对象现在已经有了,但是谁来负责创建命令呢?这里就引出了客户端Client对象,再命令模式中命令是有客户端来创建的。打个比方来说, *** 作遥控器的那个人,就是扮演的客户端的角色。人按下遥控器的不同按键,来创建一条条命令。

Invoker:现在创建命令的对象Client也已经露脸了,它负责创建一条条命令,那么谁来使用或者调度这个命令呢?--命令的使用者就是Invoker对象了,还是拿人,遥控器,电视机来做比喻,遥控器就是这个Invoker对象,遥控器负责使用客户端创建的命令对象。该Invoker对象负责要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。

上面的讲解着实有些啰嗦,下面就用看电视的人(Watcher),电视机(Television),遥控器(TeleController)来模拟一下这个命令模式,其中Watcher是Client角色,Television是Receiver角色,TeleController是Invoker角色。

首先设计一个简单的电视机的对象:

//电视机对象:提供了播放不同频道的方法

public class Television {
    
    public void playCctv1() {
        System.out.println("--CCTV1--");
    }

    public void playCctv2() {
        System.out.println("--CCTV2--");
    }

    public void playCctv3() {
        System.out.println("--CCTV3--");
    }

    public void playCctv4() {
        System.out.println("--CCTV4--");
    }

    public void playCctv5() {
        System.out.println("--CCTV5--");
    }

    public void playCctv6() {
        System.out.println("--CCTV6--");
    }

}

电视机的类创建好了,本文会以“非命令模式“和“命令模式“两种实现看电视的不同之处,加深对命令模式的理解。

非命令模式实现:

如果不用命令模式的话,其实实现看电视的功能很简单,首先设计一个看电视的人的类:Watcher;既然要看电视,所以Watcher内部需要持有一个电视机的引用。如此简单的Watcher搞定:

//观看电视的死宅类
public class Watcher {
    //持有一个
    public Television tv;

    public Watcher(Television tv) {
        this.tv = tv;
    }

    public void playCctv1() {
        tv.playCctv1();
    }

    public void playCctv2() {
        tv.playCctv2();
    }

    public void playCctv3() {
        tv.playCctv3();
    }

    public void playCctv4() {
        tv.playCctv4();
    }

    public void playCctv5() {
        tv.playCctv5();
    }

    public void playCctv6() {
        tv.playCctv6();
    }
}

所以简单的调用就实现了:

public static void main(String args[]) {
        Watcher watcher = new Watcher(new Television());
        watcher.playCctv1();
        watcher.playCctv2();
        watcher.playCctv3();
        watcher.playCctv4();
        watcher.playCctv5();
        watcher.playCctv6();
    }

执行结果:

--CCTV1--
--CCTV2--
--CCTV3--
--CCTV4--
--CCTV5--
--CCTV6--

可以看出Watcher类和Television完全的耦合在一起了,目前本文的电视机对象只能播放六个电视台,如果需要增添全国所有主流卫视的话,需要做如下改动:
1、修改Television对象,增加若干个的playXXTV()的方法来播放不同的卫视。
2、修改Watcher,也添加若干个对应的playXXTV()的方法,调用Television的playXXTV(),如下:

 public void playXXTV() {
        tv.playXXTV();
    }

但是这明显违背了“对修改关闭,对扩展开放“的重要设计原则。
别的不说,就拿本看电视来说,比如调用playXXTV()的顺序是随即的,也就是你胡乱切换了一通:比如你沿着cctv1、cctv2、cctv3、cctv4、xxtv、yytv…nntv的顺序来看电视,也就是发生了如下调用:

watcher.playCctv1();
watcher.playCctv2();
watcher.playCctv3();
watcher.playCctv4();
watcher.playCctv5();
watcher.playCctv6();
watcher.playXXtv();
watcher.playYYtv();
watcher.playNNtv();

当前你在看nntv,如果你想看上一个看过的台也就是yytv,这很简单,只要在playNNtv() 后面,调用watcher.playYYtv();即可,但是如果你想要一直回退到cctv1呢?那么你就话发生如下可怕的调用:

watcher.playCctv1();
watcher.playCctv2();
watcher.playCctv3();
watcher.playCctv4();
watcher.playCctv5();
watcher.playCctv6();
watcher.playXXtv();
watcher.playYYtv();
watcher.playNNtv();


watcher.playYYtv();
watcher.playXXtv();
watcher.playCctv6();
watcher.playCctv5();
watcher.playCctv4();
watcher.playCctv3();
watcher.playCctv2();
watcher.playCctv1();

为什么会这样呢?因为对于之前播放的哪个卫视并没有记录功能。是时候让命令模式来出来解决 问题了,通过命令模式的实现,对比下就能体会到命令模式的巧妙之处。

命令模式的实现:

1、设计一个抽象的命令类:
在本系统中,命令的接收者对象就是电视机Tevevision了:

public abstract class Command {
    //命令接收者:电视机
    protected Television television;

    public Command(Television television) {
        this.television = television;
    }

    //命令执行
    abstract void execute();
}

各个卫视的 *** 作封装成一个一个命令,实现如下:

//播放cctv1的命令
public class CCTV1Command extends Command {
    @Override
    void execute() {
        television.playCctv1();
    }
}

//播放cctv2的命令
public class CCTV6Command extends Command {
    @Override
    void execute() {
        television.playCctv2();
    }
}
。。。。。。。。

//播放cctv6的命令
public class CCTV1Command extends Command {
    @Override
    void execute() {
        television.playCctv6();
    }
}

抽象类Command的几个子类实现也很简单,就是将电视机TeleVision对象的playXxTV方法分布于不同的命令对象中,且因为不同的命令对象拥有共同的抽象类,我们很容易将这些名利功能放入一个数据结构(比如数组)中来存储执行过的命令。

命令对象设计好了,那么就引入命令的调用着Invoker对象了,在此例子中电视遥控器TeleController就是扮演的这个角色:

public class TeleController {
    //播放记录
    List historyCommand = new ArrayList();

    //切换卫视
    public void switchCommand(Command command) {
        historyCommand.add(command);
        command.execute();
    }

    //遥控器返回命令
    public void back() {
        if (historyCommand.isEmpty()) {
            return;
        }
        int size = historyCommand.size();
        int preIndex = size-2<=0?0:size-2;

        //获取上一个播放某卫视的命令
        Command preCommand = historyCommand.remove(preIndex);

        preCommand.execute();
    }

}

遥控器对象持有一个命令集合,用来记录已经执行过的命令。新的命令对像作为switchCommand参数来添加到集合中,注意在这里就体现出了让上文所术的请求参数化的目的。且遥控器类也提供了back方法用来模拟真实遥控器的返回功能:
所以main方法的实现如下:

   //创建一个电视机
    Television tv = new Television();
    //创建一个遥控器
    TeleController teleController = new TeleController();
    teleController.switchCommand(new CCTV1Command(tv));
    teleController.switchCommand(new CCTV2Command(tv));
    teleController.switchCommand(new CCTV4Command(tv));
    teleController.switchCommand(new CCTV3Command(tv));
    teleController.switchCommand(new CCTV5Command(tv));
    teleController.switchCommand(new CCTV1Command(tv));
    teleController.switchCommand(new CCTV6Command(tv));
    System.out.println("------返回上一个卫视--------");
    //模拟遥控器返回键
    teleController.back();
    teleController.back();

执行结果如下:

--CCTV1--
--CCTV2--
--CCTV4--
--CCTV3--
--CCTV5--
--CCTV1--
--CCTV6--
----------返回上一个卫视-------------
--CCTV1--
--CCTV5--

从上面的例子我们可以看出,命令模式的主要特点就是将请求封装成一个个命令,以命令为参数来进行切换,达到请求参数化的目的,且还能通过集合这个数据结构来存储已经执行的请求,进行回退 *** 作。而且如果需要添加新的电视频道,只需要添加新的命令类即可

而非命令模式中,看电视的人和电视耦合在一起;而新的命令模式则使用一个遥控器就将人和电视机解耦。总让你抱着电视机你也不乐意不是?

结合上述例子,最后用一个图来简单的表示命令模式。博主喜欢“斗图".


以上来之
本文链接:https://blog.csdn.net/chunqiuwei/article/details/79030816

我根据上面的案例改造了一下
Client角色

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner scanner = new Scanner(System.in);
        String key = null;
        //拿出来个遥控器
        TeleController teleController = new TeleController();
		System.out.println("请输入按键");
        key = scanner.next();
        while (!"0".equals(key)) {
            if (".".equals(key)) {
                teleController.back();
            } else {
                Command chess = ChessFactory.getChess(key);
                teleController.switchCommand(chess);
            }
			System.out.println("请输入按键");
			key = scanner.next();
        }
    }

}

添加一个 CommandFactory 使用简单工厂加享元模式 来获取按键事件

import java.util.HashMap;
import java.util.Map;

public class CommandFactory {
    private static final Map chessMap = new HashMap<>();
    private static Television tv= new Television();
    public static Command getChess(String key) {
        Command chess = chessMap.get(key);
        if (chess == null) {
            switch(key){
                case "1" :
                    chess=  new CCTV1Command(tv);
                    break;
                case "2" :
                    chess=  new CCTV2Command(tv);
                    break;
                case "3" :
                    chess=  new CCTV3Command(tv);
                    break;
            }
            chessMap.put(key, chess);
        }
        return chess;
    }
}

结果 用输入模拟按键
请输入按键

1
--CCTV1--
请输入按键
2
--CCTV2--
请输入按键
.
--CCTV1--
请输入按键
3
--CCTV3--
请输入按键
1
--CCTV1--
请输入按键
0
介绍

意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。

关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口

应用实例:struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。

优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景:认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

注意事项:系统需要支持命令的撤销(Undo) *** 作和恢复(Redo) *** 作,也可以考虑使用命令模式,见命令模式的扩展。

命令模式结构示意图:

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5697744.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存