C#设计模式之15——命令模式

职责链沿着类组成的链转发请求,而命令模式只是把请求转发给某个特定的对象。把针对某种特定行为的请求封装在一个对象中,并为请求指定一个大家所知的共有接口。让客户端可以实现无需对将要完成的实际行为做任何了解的情况下创建请求,并允许在不影响客户端程序的情况下以任何方式修改行为。

我们的C#桌面应用程序中,按钮的代码一般都会自动的生成一个函数,与应用程序界面代码混合在一起,但是如果按钮多了的话,对于代码的管理就不是那么方便了;而且如果多个按钮要实现相同的功能,这样就可能在界面代码中出现多次重复的相同的功能代码。

这里我们解决的方法是使用命令模式,将应用程序的界面代码和功能代码分离开来。然后通过一个共有的接口来调用功能代码。

一种确保每个对象都直接接收自己的命令的方式是使用命令模式并创建自己的命令对象,把每个按钮的功能代码封装到自己新生成的类中,然后使用共同的接口,在界面代码中就可以通过接口调用这个共同的接口了。从而实现将功能代码和界面代码分离开来。

命令对象至少要实现下面这一接口:

using System;

namespace ButtonMenu
{
	/// 
	/// interface for Command type
	/// 
	public interface Command 	{
		void Execute();
	}
}


使用这一接口的目的是把用户界面代码和程序必须执行的功能代码分离开来,调用的代码如下:

private void commandClick(object sender, System.EventArgs e) {
			Command comd = (Command)sender;
			comd.Execute ();
		}


这一事件可被连接到每一个可被单击的界面元素上。

我们需要为每一个执行所需行为的对象提供一个Execute()方法,从而将要完成的工作放在所属的对象的内部,而不是由程序的其他部分来完成这些功能。

构建命令对象:

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace ButtonMenu
{
	/// 
	/// Summary description for RedButton.
	/// 
	public class RedButton : System.Windows.Forms.Button, Command 	{
		//A Command button that turns the background red
		private System.ComponentModel.Container components = null;
		//-----
		public  void Execute() {
			Control c = this.Parent;
			c.BackColor =Color.Red ;
			this.BackColor =Color.LightGray  ;
		}
		public RedButton() 		{
			InitializeComponent();
		}

		///  
		/// Clean up any resources being used.
		/// 
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Component Designer generated code
		///  
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// 
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion
	}
}


 

这个Button控件执行了自定义的操作,把背景色设置为红色。

然后有两个自己定义的菜单类:

using System;
using System.Windows.Forms;

namespace ButtonMenu
{
	/// 
	/// Summary description for FileOpen.
	/// 
	public class FileOpen : System.Windows.Forms.MenuItem , Command
	{
		public FileOpen():base("Open")
		{
			}
		public void Execute() {
			OpenFileDialog fd = new OpenFileDialog ();
			fd.ShowDialog ();
		}
	}
}


 

using System;
using System.Windows.Forms;
namespace ButtonMenu
{
	/// 
	/// Summary description for FileExit.
	/// 
	public class FileExit :MenuItem, Command 	{
		private Form form;
		//----------
		public FileExit(Form frm) :base ("Exit") {
			form = frm;
		}
		//----------
		public void Execute() {
			form.Close ();
		}
	}
}


 

这两个菜单类都封装了要执行的具体功能,并且有着统一的接口。

然后我们在主程序中添加菜单,并且给按钮注册事件:

private ButtonMenu.RedButton btRed;
        private System.Windows.Forms.MainMenu mainMenu1;
        private IContainer components;

		public Form1() 		{
			InitializeComponent();
			init();
		}
		private void init() {
			//create a main menu and install it
			MainMenu main = new MainMenu();
			this.Menu =main;
			
			//create a click-event handler
			EventHandler evh = new EventHandler (commandClick);
			btRed.Click += evh;		//add to existing red button

			//create a "File" top level entry
			MenuItem file = new MenuItem("File");

			//create File Open command
			FileOpen mnflo = new FileOpen ();
			mnflo.Click += evh;		//add same handler
			main.MenuItems.Add ( file );

			//create a File-Exit command
			FileExit fex = new FileExit(this);
			file.MenuItems.AddRange( new MenuItem[]{ mnflo, fex});	
			fex.Click += evh;		//add same handler
		}


这样,每一个可以点击的可视化对象都注册了相同函数,因为所有的行为都继承了Command接口,有着共同的行为接口,所以我们这里只需要一个功能函数。成功的将功能的实现代码和用户界面分离开来,每个可视化类的功能代码都在自己的类的内部,并且提供一个共同的访问接口。

命令模式的主要缺点似乎是增生出了一些小的类,这些类使程序变得散乱起来。但是一般情况下这并没有带来太大了的坏处,主要的不同就是命令模式产生的小类更具可读性一些。

 

但是,虽然把行为封装在命令对象中尤其好处所在,但是把对象封装到引发行为的元素中则并不是命令模式确切要做的事情。命令对象要与调用客户端隔离开来,这里才能够独立的改变调用程序的命令和命令的实现细节。简单的说,就是在上面的基础上,上面已经实现了将功能的实现与用户界面的代码分开,在上面实现的基础上,把命令和可视化控件再分离开来。让可视化控件可以设置命令,即设置要完成的功能,这样更有利于今后程序的改动,可以随意的改动要实现的命令的功能,更符合面向对象的设计原则。

 

这样我们需要把可视化控件变成独立存在的命令对象的容器:

using System;

namespace CHolder
{
	/// 
	/// Summary description for CommandHolder.
	/// 
	public interface CommandHolder 	{
		Command getCommand();
		void setCommand(Command cmd);
	}
}


然后所有的可视化对象都实现这一接口,保证可以设置命令和返回命令:

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace CHolder
{
	/// 
	/// Summary description for ComdButton.
	/// 
	public class ComdButton : Button, CommandHolder 	{
		///  
		/// Required designer variable.
		/// 
		private System.ComponentModel.Container components = null;
		private Command command;
		public ComdButton()		{
					InitializeComponent();
		}
		public void setCommand(Command comd) {
			command = comd;
		}
		public Command getCommand() {
			return command;
		}

		///  
		/// Clean up any resources being used.
		/// 
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Component Designer generated code
		///  
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// 
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion
	}
}


 

using System;
using System.Windows.Forms;
namespace CHolder
{
	/// 
	/// Summary description for CommandMenu.
	/// 
	public class CommandMenu : MenuItem, CommandHolder 	{
		private Command command;
		public CommandMenu(string name):base(name) 	{}
		//-----	
		public void setCommand (Command comd) {
			command = comd;
		}
		//-----	
		 public Command getCommand () {
			return command;
		}
	}
}


这两个类都可以设置命令并返回命令。然后我们需要设计具体的命令了,这些命令中实现了相应的功能操作。

using System;
using System.Windows.Forms;
namespace CHolder
{
	/// 
	/// Summary description for ExitCommand.
	/// 
	public class ExitCommand : Command 
	{
		private Form form;
		public ExitCommand(Form frm)
		{
			form = frm;
		}
		public void Execute() {
			form.Close ();
		}
	}
}
using System;
using System.Windows.Forms ;
namespace CHolder
{
	/// 
	/// Summary description for OpenCommand.
	/// 
	public class OpenCommand :Command 	{
		public OpenCommand()
		{}
		public void Execute() {
			OpenFileDialog fd = new OpenFileDialog ();
			fd.ShowDialog ();
		}
	}
}


 

using System;
using System.Windows.Forms;
using System.Drawing ;
namespace CHolder
{
	/// 
	/// Summary description for cmdRed.
	/// 
	public class RedCommand : Command 	{
		private Control window;
		//-----
		public RedCommand(Control win) 		{
			window = win;
		}
		//-----
		void Command.Execute () {
			window.BackColor =Color.Red ;
		}
	}
}


我们只要创建菜单的实例并把不同的标签和命令对象传递给他们就可以了。这样就实现了给不同的对象添加不同的命令,或者可以方便的修改一个对象的命令等操作。

private ComdButton btRed;
		/// 
		/// Required designer variable.
		/// 
		private System.ComponentModel.Container components = null;

		public Form1() 		{
			InitializeComponent();
			init();
		}
		private void init() {
			//create a main menu and install it
			MainMenu main = new MainMenu();
			this.Menu =main;
			
			//create a click-event handler
			//note: btRed was added in the IDE
			EventHandler evh = new EventHandler (commandClick);
			btRed.Click += evh;		//add to existing red button
			RedCommand cRed = new RedCommand (this);
			btRed.setCommand (cRed);

			//create a "File" top level entry
			MenuItem file = new CommandMenu("File");
			main.MenuItems.Add ( file );
			//create File Open command
			CommandMenu mnuFlo = new CommandMenu("Open");
			mnuFlo.setCommand (new OpenCommand ());
			mnuFlo.Click += evh;		//add same handler
			
			//create a Red menu item, too
			CommandMenu mnuRed = new CommandMenu("Red");
			mnuRed.setCommand(cRed);
			mnuRed.Click += evh;		//add same handler
			
			//create a File-Exit command
			CommandMenu mnuFex = new CommandMenu("Exit");
			mnuFex.setCommand (new ExitCommand(this));
			file.MenuItems.AddRange( new CommandMenu[]{ mnuFlo, mnuRed, mnuFex});	
			mnuFex.Click += evh;		//add same handler
		}



使用命令模式的另外一个主要原因是,该类模式提供了一种便利的方式来存储和执行撤销功能。我们需要修改Command 接口,包含Undo()函数:

using System;

namespace UndoCommand
{
	/// 
	/// defines Command interface
	/// 
	public interface Command 	{
		void Execute();
		void Undo();
		bool isUndo();
	}
}

 

 

 

用户界面图如上图所示。

我们需要设计每个命令对象,让其保持其最后一次行为的记录。并且设计一个UndoComd类,记录每次执行的操作。

using System;
using System.Drawing ;
using System.Windows.Forms;
using System.Collections ;

namespace UndoCommand
{
	/// 
	/// Summary description for UndoCommand.
	/// 
	public class UndoComd:Command 	{
		private ArrayList undoList;
		public UndoComd() 	{
			undoList = new ArrayList ();
		}
		//-----
		public void add(Command comd) {
			if(! comd.isUndo ()) {
				undoList.Add (comd);
			}
		}
		//-----
		public bool isUndo() {
			return true;
		}
		//-----
		public void Undo() {		}
		//-----
		public void Execute() {
			int index = undoList.Count - 1;
			if (index >= 0) {
				Command cmd = (Command)undoList[index];
				cmd.Undo();
				undoList.RemoveAt(index);
			}
		}
	}
}


这样如果有撤销,则从已经执行的列表中选择最后一个执行的命令删除。

在用户界面中画了两种颜色的线,可以定义一个两种颜色线的共同基类

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections ;

namespace UndoCommand
{
	/// 
	/// Summary description for ColorCommand.
	/// 
	public class ColorCommand :Command
	{
		protected Color color;			//line color
		protected PictureBox pbox;		//box to draw in
		protected ArrayList drawList;	//list of lines
		protected int x, y, dx, dy;		//coordinates
		//------
		public ColorCommand(PictureBox pict)
		{
			pbox = pict;	//copy in picture box
			drawList = new ArrayList ();	//create list
		}
		//------
		public void Execute() {
			//create a new line to draw
			DrawData dl = new DrawData(x, y, dx, dy);
			drawList.Add(dl);	//add it to the list
			x = x + dx;			//increment the positions
			y = y + dy;
			pbox.Refresh();
		}
		//-----
		public bool isUndo() {
			return false;
		}
		//-----
		public void Undo() {
			DrawData dl;
			int index = drawList.Count - 1;
			if (index >= 0) {
				dl = (DrawData)drawList[index];
				drawList.RemoveAt(index);
				x = dl.getX();
				y = dl.getY();
			}
			pbox.Refresh();
		}
		//-----
		public void draw(Graphics g) {
			Pen rpen = new Pen(color, 1);
			int h = pbox.Height;
			int w = pbox.Width;
		//draw all the lines in the list
			for (int i = 0; i < drawList.Count ; i++) {
				DrawData dl = (DrawData)drawList[i];
				g.DrawLine(rpen, dl.getX(), dl.getY(), dl.getX() + dx, dl.getDy() + h);
			}
		}

	}
}



 

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections ;

namespace UndoCommand
{
	/// 
	/// draws bluelines and caches list for undo
	/// 
	public class BlueCommand :ColorCommand 	{
		public BlueCommand(PictureBox pict):base(pict) {
			color = Color.Blue ;
			x = pbox.Width ;
			dx = -20;
			y = 0;
			dy = 0;
		}
	}
}


 

using System;

namespace UndoCommand
{
	/// 
	/// defines Command interface
	/// 
	public interface Command 	{
		void Execute();
		void Undo();
		bool isUndo();
	}
}


 

using System;

namespace UndoCommand
{
	/// 
	/// Summary description for DrawData.
	/// 
	public class DrawData
	{
		private int x, y, dx, dy;
		public DrawData(int x_, int y_, int dx_, int dy_) 		{
			x = x_;
			dx = dx_;
			y = y_;
			dy = dy_;
		}
		public int getX() {
			return x;
		}
		public int getY() {
			return y;
		}
		public int getDx() {
			return dx;
		}
		public int getDy() {
			return dy;
		}
	}
}


这样在每次都撤销动作的时候,都会通过UndoComd类的实例删除一个已经执行的命令,然后分别用红色线或者蓝色线的实例删除最后执行的一个绘制工作,然后重新绘制所有的线。

private BlueCommand blueC;
		private RedCommand redC;
		private UndoComd undoC;

		public Form1() 		{
			InitializeComponent();
			init();
		}
		private void init() {
			btBlue.setCommand (blueC=new BlueCommand (pBox));
			btRed.setCommand (redC=new RedCommand (pBox));
			btUndo.setCommand (undoC = new UndoComd ());
			
			EventHandler evh = new EventHandler (commandClick);
			btBlue.Click += evh;
			btRed.Click += evh;
			btUndo.Click += evh;
			pBox.Paint += new PaintEventHandler (paintHandler);
		
		}
		private void commandClick(object sender, EventArgs e) {
			//get the command
			Command comd = ((CommandHolder)sender).getCommand ();
			undoC.add (comd);	//add to undo list
			comd.Execute ();	//and execute it
		}
		public void paintHandler(object sender, PaintEventArgs e) {
			Graphics g = e.Graphics ;
			blueC.draw(g);
			redC.draw (g);
		}


 

这种方式给人的感觉好像是刚才刚刚绘制的线被取消了,而实际上的实现方式是通过记录所有的线的个数,然后重新绘制,系统开销比较大,因为这里总的系统开销不是很大,所以看不出来延迟的效果,给人感觉好像是刚刚绘制的线被取消了,但是实际上这并不是很好的方式。

你可能感兴趣的:(Design,Pattern)