美文网首页
命令模式与访问者模式

命令模式与访问者模式

作者: 攻城老狮 | 来源:发表于2020-06-27 11:23 被阅读0次

    命令模式与访问者模式

    参考教程:https://www.bilibili.com/video/BV1G4411c7N4
    代码实现 Github:https://github.com/yaokuku123/pattern


    命令模式

    1. 案例
    1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。 2) 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。 3) 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式。 4) 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来. 5) 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品。
    1. 命令模式

    解释:在软件设计中,我们经常需要 向某些对象发送请求,但是并不知道请求的接收者是谁,也不知 道被请求的操作是哪个。我们只需在程序运行时指定具体的请求接收者即可,此时,可以 使用命令模式来进行设计。

    通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色: 将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。

    1. 代码实现
    1-1592895044540.png

    对原理类图的说明-即(命名模式的角色及职责)

    1. Invoker 是调用者角色

    2. Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类

    3. Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作

    4. ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute

    2-1592895144439.png
    package com.yqj.pattern.command;
    
    //具体执行者
    class LightReceiver{
        public void on(){
            System.out.println("开灯");
        }
    
        public void off(){
            System.out.println("关灯");
        }
    }
    
    //命令接口
    interface Command{
        //执行命令
        public void execute();
        //撤销操作
        public void undo();
    }
    
    //具体命令,开灯
    class LightOnCommand implements Command{
        //聚合具体的执行者
        private LightReceiver lightReceiver;
        //传入执行者
        public LightOnCommand(LightReceiver lightReceiver) {
            this.lightReceiver = lightReceiver;
        }
    
        @Override
        public void execute() {
            //调用执行者的方法
            lightReceiver.on();
        }
    
        @Override
        public void undo() {
            //调用执行者的方法
            lightReceiver.off();
        }
    }
    
    //具体命令,关灯
    class LightOffCommand implements Command{
    
        private LightReceiver lightReceiver;
    
        public LightOffCommand(LightReceiver lightReceiver) {
            this.lightReceiver = lightReceiver;
        }
    
        @Override
        public void execute() {
            lightReceiver.off();
        }
    
        @Override
        public void undo() {
            lightReceiver.on();
        }
    }
    
    //具体命令,空实现,方便初始化按钮。调用的时候省掉对空的判断
    class NoCommand implements Command{
    
        @Override
        public void execute() {
    
        }
    
        @Override
        public void undo() {
    
        }
    }
    
    class RemoteController{
        //按钮命令数值
        private Command[] onCommands;
        private Command[] offCommands;
        //撤销命令,记录上次的操作
        private Command undoCommand;
    
        public RemoteController() {
            //初始化按钮
            onCommands = new Command[5];
            offCommands = new Command[5];
            undoCommand = new NoCommand();
            for (int i=0 ; i<5 ; i++){
               onCommands[i] = new NoCommand();
               offCommands[i] = new NoCommand();
            }
        }
    
        //给按钮设置具体的命令
        public void setCommand(int index,Command onCommand,Command offCommand){
            onCommands[index] = onCommand;
            offCommands[index] = offCommand;
        }
    
        //按下开按钮
        public void onButtonWasPushed(int index){
            //找到按下的按钮,并调用该按钮的方法
            onCommands[index].execute();
            //记录操作,用于撤销
            undoCommand = onCommands[index];
        }
    
        //按下关按钮
        public void offButtonWasPushed(int index){
            offCommands[index].execute();
            undoCommand = offCommands[index];
        }
    
        //按撤销按钮
        public void undoButtonWasPushed(){
            undoCommand.undo();
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            //创建电灯对象(执行者)
            LightReceiver lightReceiver = new LightReceiver();
            //创建开,关灯的命令对象
            LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
            LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
            //创建遥控器
            RemoteController remoteController = new RemoteController();
            //设置按钮对应的命令
            remoteController.setCommand(0,lightOnCommand,lightOffCommand);
            //按下按钮
            remoteController.onButtonWasPushed(0);
            remoteController.offButtonWasPushed(0);
            remoteController.undoButtonWasPushed();
        }
    }
    
    1. 小结
    • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
    • 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
    • 容易实现对请求的撤销和重做
    • 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
    • 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
    • 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制

    访问者模式

    1. 案例

    将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如 成功、失败 等)。

    1. 传统方案
    2-1592896800769.png
    • 分析
    1. 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则, 不利于维护 2) 扩展性不好,比如:增加了新的人员类型或者评价种类都不好做 3) 引出我们会使用新的设计模式 – 访问者模式
    1. 访问者模式

    解释:封装一些作用于某种数据结构的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。

    原理:在被访问的类里面加一个对外提供接待访问者的接口。

    场景:需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。

    1. 代码实现
    3-1592897022379.png

    对原理类图的说明即(访问者模式的角色及职责)

    1. Visitor 是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作

    2. ConcreteVisitor :是一个具体的访问者实现每个有Visitor声明的操作,是每个操作实现的部分.

    3. ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素

    4. Element 定义一个accept 方法,接收一个访问者对象

    5. ConcreteElement 为具体元素,实现了accept 方法

    4-1592897205753.png
    package com.yqj.pattern.visitor;
    
    import java.util.ArrayList;
    import java.util.List;
    
    abstract class People{
        private String name;
    
        public People(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public abstract void accept(Action action);
    }
    
    class Man extends People{
    
        public Man(String name) {
            super(name);
        }
    
        //双分派,首先将具体状态action作为参数传递到man中(第一次分派)
        //然后man类调用作为参数的action的具体方法getManResult(),同时将自己this作为参数传入(第二次分派)
        @Override
        public void accept(Action action) {
            action.getManResult(this);
        }
    }
    
    class Woman extends People{
    
        public Woman(String name) {
            super(name);
        }
    
        @Override
        public void accept(Action action) {
            action.getWomanResult(this);
        }
    }
    
    abstract class Action{
        //得到男的评价
        public abstract void getManResult(Man man);
        //得到女的评价
        public abstract void getWomanResult(Woman woman);
    }
    
    class Success extends Action{
    
        @Override
        public void getManResult(Man man) {
            System.out.println("男的赞同 "+ man.getName());
        }
    
        @Override
        public void getWomanResult(Woman woman) {
            System.out.println("女的赞同 " + woman.getName());
        }
    }
    
    class Fail extends Action{
    
        @Override
        public void getManResult(Man man) {
            System.out.println("男的反对" + man.getName());
        }
    
        @Override
        public void getWomanResult(Woman woman) {
            System.out.println("女的反对" + woman.getName());
        }
    }
    
    //数据结构,维护和管理很多人
    class ObjectStructure{
        //维护集合
        private List<People> elements = new ArrayList<>();
        //添加一个人
        public void add(People p){
            elements.add(p);
        }
        //删除一个人
        public void remove(People p){
            elements.remove(p);
        }
        //显示测评情况
        public void display(Action action){
            for (People element : elements) {
                element.accept(action);
            }
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            //创建两个评价标准
            Action success = new Success();
            Action fail = new Fail();
            //创建访问者对象
            Man bob = new Man("bob");
            Man tom = new Man("tom");
            Woman alice = new Woman("alice");
            //高层遍历访问者的对象
            ObjectStructure objectStructure = new ObjectStructure();
            //加入集合
            objectStructure.add(bob);
            objectStructure.add(tom);
            objectStructure.add(alice);
            //评价
            objectStructure.display(success);
            objectStructure.display(fail);
    
        }
    }
    

    双分派:所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。 双分派意味着得到执行的操作取决于请求的种类两个接收者的类型。假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码。

    1. 小结

    优点 :

    1. 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高

    2. 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

    缺点 :

    1. 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难

    2. 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素 3) 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。

    相关文章

      网友评论

          本文标题:命令模式与访问者模式

          本文链接:https://www.haomeiwen.com/subject/qkpmfktx.html