欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式——命令模式

程序员文章站 2024-02-07 23:15:10
...

什么是命令模式

将请求封装成对象,这可以让你使用不同的请求、队列,或者日志请求来参数化其他对象。命令模式也可以支持撤销操作。

当需要将发出请求的对象和执行请求的对象解耦的时候,可以考虑使用命令模式。

 

命令模式的角色

Command: 抽象命令类

ConcreteCommand: 具体命令类

Invoker: 调用者

Receiver: 接收者

Client:客户类

 

设计模式——命令模式

 

实例(这里偷懒一下,直接搬运了经典实现):

/// <summary>
    /// Command角色
    /// </summary>
    public interface ICommand
    {
        void Execute();
    }

    /// <summary>
    /// ConcreteCommand角色A
    /// </summary>
    public class ConcreteCommandA : ICommand
    {
        private Receiver receiver = null;

        public ConcreteCommandA(Receiver receiver)
        {
            this.receiver = receiver;
        }

        public void Execute()
        {
            this.receiver.DoA();
        }
    }

    /// <summary>
    /// ConcreteCommand角色B
    /// </summary>
    public class ConcreteCommandB : ICommand
    {
        private Receiver receiver = null;

        public ConcreteCommandB(Receiver receiver)
        {
            this.receiver = receiver;
        }

        public void Execute()
        {
            this.receiver.DoB();
        }
    }

    /// <summary>
    /// Receiver角色
    /// </summary>
    public class Receiver
    {
        public void DoA()
        {
            //DoSomething
        }

        public void DoB()
        {
            //DoSomething
        }
    }

    /// <summary>
    /// Invoker角色
    /// </summary>
    public class Invoker
    {
        private ICommand command = null;

        //设置命令
        public void SetCommand(ICommand command)
        {
            this.command = command;
        }
        //执行命令
        public void RunCommand()
        {
            command.Execute();
        }
    }

    /// <summary>
    /// 客户端调用
    /// </summary>
    public class Client
    {
        public Client()
        {
            Receiver receiver = new Receiver();
            Invoker invoker = new Invoker();
            invoker.SetCommand(new ConcreteCommandA(receiver));
            invoker.RunCommand();
            invoker.SetCommand(new ConcreteCommandB(receiver));
            invoker.RunCommand();
        }
    }

 

第一次看到这个实现,给我的感觉是繁杂,直接使用下列代码,不是更加简洁直观吗?

public class Client
    {
        public Client()
        {
            Receiver receiver = new Receiver();
            receiver.DoA();
            receiver.DoB();
        }
    }

但是这么写,其实是将命令的请求者与命令的执行过程绑定在了一起,这是一种耦合,上述代码抽象一下就是下图:

设计模式——命令模式

我们将命令的执行写在了客户端的代码里,这意味着客户需要自己编写大量的命令逻辑代码,繁琐且容易出错,为了绕开这种弊端,我们可以对命令的执行进行封装,即:

 

设计模式——命令模式

 

这样,客户就不需要编写大量命令逻辑代码了,但这又会有问题:

设计模式——命令模式

可以看到,两个负责命令执行的类都执行了命令A,意味着有些代码是重复编写了,那我们在改进一下:

设计模式——命令模式

 

至此,看起来似乎没有什么问题了,但是,如果我们想在所有命令执行之前或是之后都进行一些统一的操作,例如写入日志等操作,把这些操作写入到命令执行者或是命令中?不合适,因为这会导致类的职责混乱,命令只负责完成一些动作,命令执行者只负责调用命令,那么我们在改一下:

设计模式——命令模式

 

命令执行前后的一些操作都可以通过命令调用者来执行,并且,命令调用者向client屏蔽了命令执行的细节(不在由client直接调用命令执行者执行命令),client不知道自己的命令什么时候被执行,有没有被执行,封装性得到提高,到此,看起来似乎很完美,但是还有一点美中不足的地方,上述结构中,有多少个命令执行者,命令调用者就要持有多少个命令执行者,代码编写繁琐且不易于扩展,那我们在改一下:

设计模式——命令模式

 

至此,命令模式的雏形就出来了,命令调用者只需要含有一个接口即可,接口的存在使整个系统容易扩展,满足开闭原则,客户端自己决定调用什么命令执行者,提供命令对象给命令执行者,通过命令调用者来调用命令,完成自己所需要的功能,之前逛帖子时看到一个这样的问题——为什么要把命令对象暴露给客户端?我自己的理解是命令本身是有参数的,例如sql语句,表明、字段这些都是可以变化的,这些和命令本身有关,而命令执行者只负责执行命令,不负责命令的初始化,因此有必要把命令本身暴露出来,让客户端自己初始化