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