设计模式复习(十六)-------命令模式

1. 定义

耦合与变化
不恰当的紧密耦合是软件不能抵御需求变化的根本原因。不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系。
创建型设计模式解决的创建者和被创建对象间的耦合问题;
结构型设计模式解决的是实体对象和实体对象间的耦合问题;
行为型设计模式解决的是实体对象和行为操作之间的耦合问题。

  • 在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
  • 如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

将一个请求封装为一个对象,从而使你可用不同的请求对客户(客户程序,也是行为的请求者)进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
——《设计模式》GoF

2. 结构

在这里插入图片描述

  • 客户(Client)角色:创建一个(或多个)具体命令(ConcreteCommand)对象并确定其接收者。
  • 命令(Command)角色:声明一个所有具体命令类的抽象接口。这是一个抽象角色。
  • 具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收者的相应操作,Execute()方法通常叫做执行方法。
  • 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动(Action)方法,可以实现记录、回退/重做等操作。
  • 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动(Action)方法。

典型的抽象命令类代码:

abstract class Command{
    public abstract void Execute();
}

典型的调用者(请求发送者)类代码:

class Invoker{
    private Command command;	
    //构造注入
    public Invoker(Command command){this.command=command;}	
    public Command Command{
        get { return command; }
        //设值注入
        set { command = value; } 
	}	
    //业务方法,用于调用命令类的方法
    public void Call(){
        command.Execute();
    }
}

典型的具体命令类代码:

class ConcreteCommand : Command{
	private Receiver receiver; //维持一个对请求接收者对象的引用
	public override void Execute(){
		receiver.Action();//调用请求接收者的业务处理方法Action()
	}
}

典型的请求接收者类代码:

class Receiver{
	public void Action(){
		//具体操作
	}
}

3. 示例

示例1

绘图系统:绘制的基本图形有线段、矩形和圆等,记录绘图命令统一绘图, 并可以取消已经添加的操作命令。
示例: DrawProject—不用设计模式

namespace DrawProject{
    /// <summary>
    /// 绘图--行为的实现者
    /// </summary>
    class Drawer{
        /// <summary>
        /// 绘制线段
        /// </summary>
        /// <param name="sx">线段始点坐标x</param>
        /// <param name="sy">线段始点坐标y</param>
        /// <param name="ex">线段终点坐标x</param>
        /// <param name="ey">线段终点坐标y</param>
        public void DrawLine(int sx, int sy, int ex, int ey){
            Console.WriteLine("绘制线段:({0},{1})--〉({2},{3})", sx, sy, ex, ey);
        }

        /// <summary>
        /// 绘制矩形
        /// </summary>
        /// <param name="sx">矩形左上角坐标x</param>
        /// <param name="sy">矩形左上角坐标y</param>
        /// <param name="ex">矩形右下角坐标x</param>
        /// <param name="ey">矩形右下角坐标y</param>
        public void DrawRectangle(int sx, int sy, int ex, int ey){
            Console.WriteLine("绘制矩形:({0},{1})--〉({2},{3})", sx, sy, ex, ey);
        }

        /// <summary>
        /// 绘制园
        /// </summary>
        /// <param name="cx">圆心坐标x</param>
        /// <param name="cy">圆心坐标y</param>
        /// <param name="r">圆的半径</param>
        public void DrawCircle(int cx, int cy, double r){
            Console.WriteLine("绘制圆:圆心=({0},{1}),r={2}", cx, cy, r);
        }
    }
}
namespace DrawProject{
    /// <summary>
    /// 客户端程序--行为的请求者
    /// </summary>
    class Program{
        static void Main(string[] args){
            Drawer drawer = new Drawer();

            //此处客户端程序与具体绘图类的绘图方法紧密耦合--结构较为僵化
            //而且如果需要支持“后退”或“前进”操作,则比较困难!

            drawer.DrawLine(0, 0, 10, 50);
            drawer.DrawLine(10, 50, 8, 60);
            drawer.DrawLine(8, 60, 0, 0);

            drawer.DrawRectangle(13, 20, 50, 80);
            drawer.DrawRectangle(55, 60, 100, 90);

            drawer.DrawCircle(55, 68, 12);

            Console.Read();
        }
    }
}

示例:CommandDraw

设计类图如下:
receiver一般放在具体类中。

在这里插入图片描述

namespace CommandDraw{
    /// 绘图命令抽象类--抽象命令类
    abstract class DrawCommand{
        /// 绘图命令的执行者--在此处,只有一个Receiver--如果不同具体命令使用的Receiver不同,则须定义到具体的绘图类(具体命令类)中
        protected Drawer drawer;

        /// 设置使用的绘图对象(接收者)
        /// <param name="drawer"></param>
        public void SetDrawer(Drawer drawer){
            this.drawer = drawer;
        }

        /// 绘图方法
        public abstract void ToDraw();
    }
}

namespace CommandDraw{
    /// 具体的绘制线段的命令--具体命令类
    class DrawLineCommand:DrawCommand{
        //与具体命令相关的具体状态
        private int sx, sy, ex, ey;

        /// 通过构造函数传入命令所需参数
        public DrawLineCommand(int sx, int sy, int ex, int ey){
            this.sx = sx;
            this.sy = sy;
            this.ex = ex;
            this.ey = ey;
        }

        /// 调用接受者完成具体命令操作
        public override void ToDraw(){
            drawer.DrawLine(sx, sy, ex, ey);
        }
    }
}
namespace CommandDraw{
    /// 绘制矩形的命令--具体命令类
    class DrawRectangleCommand:DrawCommand{
        //与具体命令相关的具体状态
        private int sx, sy, ex, ey;

        /// 通过构造函数传入命令所需参数
        public DrawRectangleCommand(int sx, int sy, int ex, int ey){
            this.sx = sx;
            this.sy = sy;
            this.ex = ex;
            this.ey = ey;
        }
        /// 调用接受者完成具体命令操作
        public override void ToDraw(){
            drawer.DrawRectangle(sx, sy, ex, ey);
        }
    }
}
namespace CommandDraw{
    /// 具体绘图命令--画圆----具体命令类
    class DrawCircleCommand:DrawCommand{
        //与具体命令相关的具体状态
        private int cx, cy;
        private double r;

        /// 通过构造函数传入命令所需参数
        public DrawCircleCommand(int cx, int cy, double r){
            this.cx = cx;
            this.cy = cy;
            this.r = r;
        }

        /// <summary>
        /// 调用接受者完成具体命令操作
        /// </summary>
        public override void ToDraw(){
            drawer.DrawCircle(cx, cy, r);
        }
    }
}
namespace CommandDraw{
    /// 具体绘图类--接收者--Receiver,具体的接收者可能是多个类(形成继承结构),每个类完成一个具体的功能
    class Drawer{
        /// 绘制线段
        public void DrawLine(int sx, int sy, int ex, int ey){
            Console.WriteLine("绘制线段:({0},{1})--〉({2},{3})", sx, sy, ex, ey);
        }

        /// 绘制矩形
        public void DrawRectangle(int sx, int sy, int ex, int ey){
            Console.WriteLine("绘制矩形:({0},{1})--〉({2},{3})", sx, sy, ex, ey);
        }

        /// 绘制园
        public void DrawCircle(int cx, int cy, double r){
            Console.WriteLine("绘制圆:圆心=({0},{1}),r={2}", cx, cy, r);
        }
    }
}
namespace CommandDraw{
    /// Invoker--专门的绘图命令管理类
    class DrawOperation{
        /// 绘图命令列表--命令容器,所有命令被同一个请求触发后,先后执行
        private List<DrawCommand> commandlist = new List<DrawCommand>();

        /// 添加绘图命令
        /// <param name="command">具体的绘图命令</param>
        public void AddDrawCommand(DrawCommand command)
        {
            commandlist.Add(command);
        }

        /// 取消已经添加的绘图命令
        /// <param name="command"></param>
        public void CancelDrawCommand(DrawCommand command){
            commandlist.Remove(command);
        }
        /// 执行所有的绘图命令
        public void ExecDrawCommand(){
            //依次执行所有命令
            foreach (DrawCommand c in commandlist)
                c.ToDraw();

            commandlist.Clear();//执行完请求后,清空请求列表
        }
    }
}

namespace CommandDraw{
    class Program{
        static void Main(string[] args){
            //创建绘图对象--接收者对象,各个接收者分开后,此操作也可以在具体命令类中进行
            Drawer drawer = new Drawer();
            //绘图操作--Invoker对象
            DrawOperation operation = new DrawOperation();

            //添加3个画线命令
            DrawCommand c = new DrawLineCommand(0, 0, 10, 20);
            c.SetDrawer(drawer);
            operation.AddDrawCommand(c);
            c = new DrawLineCommand(20, 30, 50, 60);
            c.SetDrawer(drawer);
            operation.AddDrawCommand(c);
            c = new DrawLineCommand(50, 60, 0, 0);
            c.SetDrawer(drawer);
            operation.AddDrawCommand(c);

            //添加2个画矩形命令
            c = new DrawRectangleCommand(30, 40, 70, 80);
            c.SetDrawer(drawer);
            operation.AddDrawCommand(c);
            DrawCommand cx = new DrawRectangleCommand(50, 50, 80, 90); //此命令后来被取消
            cx.SetDrawer(drawer);
            operation.AddDrawCommand(cx);

            //添加1个画圆命令
            c = new DrawCircleCommand(50, 50, 5.2);
            c.SetDrawer(drawer);
            operation.AddDrawCommand(c);

            //取消一个画矩形命令
            operation.CancelDrawCommand(cx);//取消命令对象

            //绘制所有图形
            operation.ExecDrawCommand();

            Console.Read();
        }
    }
}
执行结果:
绘制线段:(0,0)--〉(10,20)
绘制线段:(20,30)--〉(50,60)
绘制线段:(50,60)--〉(0,0)
绘制矩形:(30,40)--〉(70,80)
绘制圆:圆心=(50,50),r=5.2

示例2

为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统(由SystemExitClass类来实现),也可以用于显示帮助文档(由DisplayHelpClass类来实现)。
用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类(命令类)与功能类(接受者)之间解耦,即可为同一个功能键设置不同的功能。

示例:CommandSample(类图见下页)

在这里插入图片描述

(1)Invoker:FunctionButton,此例中不需要队列,只有一个命令对象;
(2)两个ConcreteCommand:ExitCommand和HelpCommand,各自有自己专用的
Receiver–SystemExitClass和DisplayHelpClass

//Command
namespace CommandSample{
    /// 抽象命令类
    abstract class Command{
        public abstract void Execute(); //具体执行命令的方法
    }
}
namespace CommandSample{
    /// 退出命令,具体命令
    class ExitCommand : Command{
        private SystemExitClass seObj;//该命令专用的Receiver

        public ExitCommand(){
            seObj = new SystemExitClass();
        }

        public override void Execute(){
            seObj.Exit();
        }
    }
}
namespace CommandSample{
    /// 帮助命令,具体命令类
    class HelpCommand : Command{
        private DisplayHelpClass hcObj; //该命令专用的Receiver

        public HelpCommand(){
            hcObj = new DisplayHelpClass();
        }

        public override void Execute(){
            hcObj.Display();
        }
    }
}
//Receiver
namespace CommandSample{
    /// 接受者--具体执行帮助命令
    class DisplayHelpClass{
        public void Display(){
            Console.WriteLine("显示帮助文档!");
        }
    }
}
namespace CommandSample{
    /// 接受者--具体执行退出命令
    class SystemExitClass{
        public void Exit(){
            Console.WriteLine("退出系统!");
        }
    }
}
namespace CommandSample{
    /// 命令队列管理--Invoker的具体底层实现--可以管理多条命令
    class CommandQueue{
        //定义一个List来存储命令队列
        private List<Command> commands = new List<Command>();

        public void AddCommand(Command command){
            commands.Add(command);
        }

        public void RemoveCommand(Command command){
            commands.Remove(command);
        }

        //循环调用每一个命令对象的Execute()方法
        public void Execute() {
		    foreach (object command in commands) {
			    ((Command)command).Execute();
		    }
	    }
    }
}
namespace CommandSample{
    /// Invoker调用者的调用接口--一个请求执行一组命令
    class Invoker{
        private CommandQueue commandQueue; //维持一个CommandQueue对象的引用

        //构造注入--构造时设定命令队列
        public Invoker(CommandQueue commandQueue){
            this.commandQueue = commandQueue;
        }

        //设值注入--专门方法设定命令序列
        public void SetCommandQueue(CommandQueue commandQueue){
            this.commandQueue = commandQueue;
        }

        //调用CommandQueue类的Execute()方法
        public void Call(){
            commandQueue.Execute();
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="command" value="CommandSample.ExitCommand"/>
  </appSettings>
</configuration>
namespace CommandSample{
    /// 调用(请求)者--Invoker
    class FunctionButton{
        private Command command; //只保存一个命令

        public Command Command{ //get/set具体命令     
            get { return command; }
            set { command = value; }
        }

        public void Click(){
            Console.WriteLine("单击功能键!");
            command.Execute(); //执行具体的命令
        }
    }
}
namespace CommandSample{
    class Program{
        static void Main(string[] args){
            //创建一个Invoker对象
            FunctionButton fb = new FunctionButton();
            
            Command command;
            //读取配置文件
            string commandStr = ConfigurationManager.AppSettings["command"];
            //反射生成命令对象
            command = (Command)Assembly.Load("CommandSample").CreateInstance(commandStr);

            //设置命令对象
            fb.Command = command;
            //执行命令
            fb.Click();

            Console.Read();
        }
    }
}

示例3

设计一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作。
示例:CommandUndoSample
在这里插入图片描述

namespace CommandUndoSample{
    /// 抽象的命令类--含有回退操作
    abstract class AbstractCommand{
        public abstract int Execute(int value); //执行
        public abstract int Undo();//回退
    }
}
namespace CommandUndoSample{
    /// 具体命令类--实现业务逻辑、回退操作
    class AddCommand : AbstractCommand{
        /// 内嵌Receiver对象--实现具体操作--随具体命令类对象一起被创建
        private Adder adder = new Adder();
        
        private int value; //上次操作的数据--用于回退,只能实现一次回退--若多次回退,需要用到堆栈

        public override int Execute(int value){
            this.value = value;
            return adder.Add(value);
        }

        /// 具体实现回退操作--实际操作内容与具体的业务逻辑相关,可能很复杂
        /// <returns></returns>
        public override int Undo(){
            return adder.Add(-value);
        }
    }
}

namespace CommandUndoSample{
    /// 接收者Receiver--真正实现具体业务逻辑操作
    class Adder{
        private int num = 0; //初值为0,连续加

        public int Add(int value){
            num += value;
            return num;
        }
    }
}

namespace CommandUndoSample{
    /// 调用者Invoker
    class CalculatorForm{
        /// 待调用的命令对象--只有一个命令对象
        private AbstractCommand command;

        public AbstractCommand Command{
            get { return command; }
            set { command = value; }
        }
        /// 业务逻辑操作
        public void Compute(int value){
            int i = Command.Execute(value);
            Console.WriteLine("执行运算,运算结果为:" + i);
        }

        /// 取消(回退)操作
        public void Undo(){
            int i = Command.Undo();
            Console.WriteLine("执行撤销,运算结果为:" + i);
        }
    }
}

namespace CommandUndoSample{
    public class Program{
        static void Main(string[] args){
            CalculatorForm form = new CalculatorForm(); //调用(请求)者
            AbstractCommand command;
            command = new AddCommand(); //具体命令
            form.Command = command; //设置要执行的命令

            form.Compute(5); //具体实行命令,+5
            form.Compute(6); //+6
            form.Compute(7); //+7
            form.Undo(); //回退,-7

            Console.Read();
        }
    }
}
执行运算,运算结果为:5
执行运算,运算结果为:11
执行运算,运算结果为:18
执行撤销,运算结果为:11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值