zoukankan      html  css  js  c++  java
  • C#设计模式之11:命令模式

    C#设计模式之11:命令模式

    命令模式

    命令模式用来解决一些复杂业务逻辑的时候会很有用,比如,你的一个方法中到处充斥着if else 这种结构的时候,用命令模式来解决这种问题就会让事情变得简单很多。

    命令模式是封装的一个全新的境界:把方法调用封装起来。通过封装方法调用,可以把运算快封装成形,所以调用此运算对象不需要知道事情是如何进行的。通过封装方法调用,可以实现一些很聪明的事,比如日志记录,比如重复使用这些封装来实现撤销(undo)。

    命令模式可将“动作的请求者”从“动作的执行者对象中解耦。

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

    下面是命令模式的UML定义:

    Command:定义命令的接口,声明执行的方法。

    ConcreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
    Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
    Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
    Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

    命令对象通过在特定的接收者上绑定一组动作来封装一个请求,要达到这一点,命令对象将动作和接收者抱紧对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟那个接收者进行了那些动作,只知道如果调用execute()方法,请求的目的就能达到。

    实现命令模式

    背景:假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:

    该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,暂时不做其功能,但是希望可以保留接口,用的时间可以方便的扩展。

    除上面的需求之外,还需要有个按钮,可以撤销上一步的操作。基本功能如下图:

     

    设计遥控器

     public class RemoteControl
        {
            private readonly ICommand[] _onCommands;
            private readonly ICommand[] _offCommands;
    
            public RemoteControl()
            {
                _onCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)
                _offCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)
                var noCommand = new NoCommand();
                for (int index = 0; index < 7; index++)
                {
                    _onCommands[index] = noCommand;
                }
            }
    
            public void SetCommand(int index, ICommand onCommand, ICommand offCommand)
            {
                _onCommands[index] = onCommand;
                _offCommands[index] = offCommand;
            }
    
            public void OnButtonWasPushed(int index)
            {
                _onCommands[index].Execute();
            }
    
            public void OffButtonWasPushed(int index)
            {
                _offCommands[index].Execute();
            }
        }

    设计命令

    上面我们初始化RemoteControl的时候用到了一个nocommand,这个命令什么都不做,我们只是将遥控器上面的插槽都初始化成一个空命令,这个空命令格式如下:

     public class NoCommand : ICommand
        {
            public void Execute()
            {
                Console.WriteLine("No command was executed!");
            }
        }

    NoCommand对象是一个空对象,当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任交给空对象,举例来说,遥控器不可能一出场就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品。当调用它的execute()方法时,这种对象什么事情也不做。

    然后,我们做一个开关灯的命令:

    public class LightOnCommand : ICommand
        {
            private readonly Light _light;//这个字段就是ConcreteCommand中的一个Receiver,多用组合少用继承!
    
            public LightOnCommand(Light light)
            {
                _light = light;
            }
    
            public void Execute()
            {
                _light.On();
            }
        }

    可以看到LightOnCommand就是一个控制灯开的命令,它对应命令模式中的ConcreteCommand,里面有一个_light字段,这个_light字段对应的是Receiver,真正的动作执行都是由Receiver来决定的。同样,我们可以设计一个关灯的Command:

     public class LightOffCommand : ICommand
        {
            private readonly Light _light;
    
            public LightOffCommand(Light light)
            {
                _light = light;
            }
    
            public void Execute()
            {
                _light.Off();
            }
        }

    代码结构基本相同,不在赘述

    一个Receiver,我们上面代码中是一个灯,他就是一个类,真正执行动作的就是这个灯,Receiver:

    public class Light
        {
            public void On()
            {
                Console.WriteLine("light's on");
            }
    
            public void Off()
            {
                Console.WriteLine("light's off");
            }
        }

    这样,关键的几个角色就已经设计完毕,我们看一下效果:

     class Program
        {
            static void Main(string[] args)
            {
                var control = new RemoteControl();
                var light = new Light();
                var lightOnCommand = new LightOnCommand(light);
                var lightOffCommand = new LightOffCommand(light);
    
                control.SetCommand(0, lightOnCommand, lightOffCommand);
                control.OnButtonWasPushed(0);
                Console.ReadKey();
            }
        }

    运行一下,效果良好,能够实现我们的目的。

    实现撤销功能

    接下来我们需要为命令设计一个撤销按钮,首先我们要给ICommand接口来设计一个Undo动作:

     public interface ICommand
        {
            void Execute();
            void Undo();
        }

    然后让关灯和开灯等实现这个新的接口方法:

     public class LightOffCommand : ICommand
        {
            private readonly Light _light;
    
            public LightOffCommand(Light light)
            {
                _light = light;
            }
    
            public void Execute()
            {
                _light.Off();
            }
    
            public void Undo()
            {
                _light.On();
            }
        }
        public class LightOnCommand : ICommand
        {
            private readonly Light _light;//这个字段就是ConcreteCommand中的一个Receiver,多用组合少用继承!
    
            public LightOnCommand(Light light)
            {
                _light = light;
            }
    
            public void Execute()
            {
                _light.On();
            }
    
            public void Undo()
            {
                _light.Off();
            }
        }

    可以看出修改这个还是非常容易的,Undo就是Execute的反向操作。

    在添加了Undo接口方法后,还有一个问题需要解决,那就是我们需要记录当前按下开关的对应的是哪一个Command,因为我们的Undo按钮只有一个。我们开始这个设计:

    public class RemoteControl
        {
            private readonly ICommand[] _onCommands;
            private readonly ICommand[] _offCommands;
            private ICommand _undoCommand;
            public RemoteControl()
            {
                _onCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)
                _offCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)       
                var noCommand = new NoCommand();
                _undoCommand = noCommand;
                for (int index = 0; index < 7; index++)
                {
                    _onCommands[index] = noCommand;
                }
            }
    
            public void SetCommand(int index, ICommand onCommand, ICommand 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();
            }
        }

    首先,我们添加了一个_undoCommand的字段,它表示当前需要执行撤销操作的那个command,然后,我们在每次执行OnButtonWasPushed或OffButtonWasPushed方法后,记录或更新这个command,让它成为一个UndoCommand。最后,添加一个UndoButtonWasPushed方法,来表示执行一个Undo操作,里面就是调用当前_undoCommand对象上面的Undo方法。

    锁的思考

    当我们完成上面的操作后,实际上还有一个问题在里面,那就是锁的操作。当我们在遥控器上面乱按一通的话,因为_undoCommand这个对象在多个方法上出现,当不同的线程都在执行这些方法时,_undoCommand这个对象的状态是会不断的变化的,如果我们不给这个对象加一个锁,那么操作就不是原子的,当我们执行UndoButtonWasPushed这个方法的时候,由于其他线程上面对于_undoCommand的访问可能没有用结束,那么这个时候我们执行撤销操作,里面的这个Undo对象可能不是最新的,我们来解决这个问题:

     public class RemoteControl
        {
            private readonly ICommand[] _onCommands;
            private readonly ICommand[] _offCommands;
            private ICommand _undoCommand;
            private readonly static object Lock = new object();//使用lock代码块的必备字段
    
            public RemoteControl()
            {
                _onCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)
                _offCommands = new ICommand[7];//因为遥控器上面只有7个插槽(插槽对应Command)       
                var noCommand = new NoCommand();
                _undoCommand = noCommand;
                for (int index = 0; index < 7; index++)
                {
                    _onCommands[index] = noCommand;
                }
            }
    
            public void SetCommand(int index, ICommand onCommand, ICommand offCommand)
            {
                _onCommands[index] = onCommand;
                _offCommands[index] = offCommand;
            }
            public void OnButtonWasPushed(int index)
            {
                _onCommands[index].Execute();
                lock (Lock)//加锁
                {
                    _undoCommand = _onCommands[index];
                }
            }
            public void OffButtonWasPushed(int index)
            {
                _offCommands[index].Execute();
                lock (Lock)//加锁
                {
                    _undoCommand = _offCommands[index];
                }
            }
            public void UndoButtonWasPushed()
            {
                lock (Lock)//加锁
                {
                    _undoCommand.Undo();
                }
            }
        }
  • 相关阅读:
    [react002] component基本用法
    [react001] 使用webpack自动构建react 项目
    [Elixir009]像GenServer一样用behaviour来规范接口
    [Elixir008]Nested Module里的动态函数调用方式
    [Elixir007] on_definition规范函数定义时的各种潜规则
    [Elixir006]CSV(Comma-separated values)处理
    [Elixir005] 查看指定数据的详细信息 i helper
    [Elixir004]通过环境变量(Environment Variables)来管理config
    [Elixir003] Mix Archives
    [Elixir002]节点启动后自动连接其它节点
  • 原文地址:https://www.cnblogs.com/pangjianxin/p/10813552.html
Copyright © 2011-2022 走看看