C#设计模式之21——状态模式

状态模式中,可以通过对象表示应用的状态,并通过切换对象来切换应用程序的状态。可以让一个类在多个被包含的相关类之间做切换,并把方法调用差地给当前的被包含类。

我们编程中,经常会遇到基于传到类中的参数来创建不同的对象,这些对象或者显示不同的信息,或者执行不同的计算。这经常会在决定执行哪种行为的类的内部导致某些类型的select case或者if-else语句的出现,如果过多的这些语句,对于程序的理解和维护是非常的困难的。

我们的例子是这样一个程序,和备忘录模式差不多。有Select, Rectangle,Circle,Fill,Undo,Clear等按钮,每个按钮被选中的时候执行不同的操作。这一图形编辑器的状态影响了程序应该展现的行为,而这促使了使用状态模式设计的提出。

如果不是使用状态模式,而是使用中介模式设计这一程序,那么我们的中介者需要了解太多的事情,很容易造成程序的混乱。

程序用户界面如图:

这些按钮引发的行为有:引发鼠标单击事件,鼠标拖动。

我们创建一个处理鼠标活动的State对象:

using System;

namespace State
{
	/// <summary>
	///empty base class containing State methods to override
	/// </summary>
	public class State     {
		//keeps state of each button
		protected Mediator med;
		public State(Mediator md) {
			med = md;	//save reference to mediator
		}
		public virtual void mouseDown(int x, int y) {}
		public virtual void mouseUp(int x, int y) {	}
		public virtual void mouseDrag(int x, int y) {}
		public virtual void selectOne(Drawing d) {}
	}
}


我们可以创建出不同的状态类,这样让不同的状态继承这个State类,一个典型的对象状态智慧重新那些他需要专门处理的事件方法。

using System;

namespace State
{
	/// <summary>
	/// Summary description for RectState.
	/// </summary>
	public class RectState :State 	{
		public RectState(Mediator md):base (md) {}
		//-----
		public override void mouseDown(int x, int y) {
			VisRectangle vr = new VisRectangle(x, y);
			med.addDrawing (vr);
		}
	}
}


 

using System;

namespace State
{
	/// <summary>
	/// Summary description for CircleState.
	/// </summary>
	public class CircleState : State 	{
		public CircleState(Mediator md):base (md){ }
		//-----
		public override void mouseDown(int x, int y) {
			VisCircle c = new VisCircle(x, y);
			med.addDrawing (c);
		}
	}
}


然后其他的状态类也是如此创建,然后FillState需要特殊处理一下,因为有两种行为,一个是选中,一个是直接填充;

using System;

namespace State
{
	/// <summary>
	/// Summary description for FillState.
	/// </summary>
	public class FillState : State 	{
		public FillState(Mediator md): base(md)	{ }
		//-----
		public override void mouseDown(int x, int y) {
			//Fill drawing if you click inside one
			int i = med.findDrawing(x, y);
			if (i >= 0) {
				Drawing d = med.getDrawing(i);
				d.setFill(true);  //fill drawing
			 }
		}
		//-----
		public override void selectOne(Drawing d) {
			//fill drawing if selected
			d.setFill (true);
		}
	}
}


 

我们创建一个状态管理的类,在这个类的内部对每种不同的状态进行切换,我们创建每种状态的一个实例,然后只是简单的currentState变量的值设置成被选中的按钮所标识的状态。

using System;

namespace State
{
	/// <summary>
	/// Summary description for StateManager.
	/// </summary>
	public class StateManager 	{
		private State currentState;
		private RectState rState;
		private ArrowState aState;
		private CircleState cState;
		private FillState fState;

		public StateManager(Mediator med) 		{
			//create an instance of each state
			rState = new RectState(med);
			cState = new CircleState(med);
			aState = new ArrowState(med);
			fState = new FillState(med);
			//and initialize them
			//set default state
			currentState = aState;
		}
		//-----
		//These methods are called when the toolbuttons are clicked
		public void setRect() {
			currentState = rState;
		}
		//-----
		public void setCircle() {
			currentState = cState;
		}
		public void setFill() {
			currentState = fState;
		}
		public void setArrow() {
			currentState = aState;
		}
		public void mouseDown(int x, int y) {
			currentState.mouseDown (x, y);
		}
		public void mouseUp(int x, int y) {
			currentState.mouseUp (x, y);
		}
		public void mouseDrag(int x, int y) {
			currentState.mouseDrag (x, y);
		}
		public void selectOne(Drawing d) {
			currentState.selectOne (d);
		}

	}
}


在这个类的内部创建每种状态的实例,并在set方法被调用的时候把正确的状态赋值给状态变量。

剩余的部分代码就是调用状态对象的方法,只要踏实当前状态便可,这里就是状态模式的优势了:没有条件检查。

中介者是一个关键的类,中介者需要告诉StateManager当前的程序状态何时会发生变化,所以中介者的开始部分要说明这一状态变化是如何发生的。

public class Mediator 	{
		private bool startRect;
		private int selectedIndex;
		private RectButton rectb;
		private bool dSelected;
		private ArrayList drawings;
		private ArrayList undoList;
		private RectButton rButton;
		private FillButton filButton;
		private CircleButton circButton;
	    private PickButton arrowButton;
		private PictureBox canvas;
		private int selectedDrawing;
		private StateManager stMgr;
		//-----
		public Mediator(PictureBox pic) 		{
			startRect = false;
			dSelected = false;
			drawings = new ArrayList();
			undoList = new ArrayList();
			stMgr = new StateManager(this);
			canvas = pic;
			selectedDrawing = -1;
		}
		//-----
		public void startRectangle() {
			stMgr.setRect();
			arrowButton.setSelected(false);
			circButton.setSelected(false);
			filButton.setSelected(false);
		}
		//-----
		public void startCircle() {
			stMgr.setCircle();
			rectb.setSelected(false);
			arrowButton.setSelected(false);
			filButton.setSelected(false);
		}


 

这里只给出了部分中介者的代码。

然后是我们的按钮的构造,按钮的构造使用了命令模式,这样每个按钮的执行的操作都在按钮中,可以在用户界面代码保持简洁清静的代码:

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

namespace State
{
	/// <summary>
	/// Summary description for ComdToolBarButton.
	/// </summary>
	public class ComdToolBarButton : ToolBarButton , Command 	{
		private System.ComponentModel.Container components = null;
		protected Mediator med;
		protected  bool selected;
		public ComdToolBarButton(string caption, Mediator md) 		{
			InitializeComponent();
			med = md;
			this.Text =caption;
		}
		//------
		public void setSelected(bool b) {
			selected = b;
			if(!selected)
				this.Pushed =false;
		}
		//-----
		public virtual void Execute() {
		}
		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

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

在界面程序中调用不同的按钮事件:

private void tBar_ButtonClick(object sender, System.Windows.Forms.ToolBarButtonClickEventArgs e) {
			Command comd = (Command)e.Button ;
			comd.Execute ();
		}


这里还有撤销列表的维护,我们使用了一个接口并使用了备忘录模式来实现撤掉的操作。

using System;
using System.Drawing ;
using CsharpPats;
namespace State
{
	/// <summary>
	/// interface defining Drawing object
	/// </summary>
	public interface Drawing 	{
		void setSelected(bool b);
		void draw(Graphics g);
		void move(int xpt, int ypt );
		bool contains(int x,int y);
	    void setFill(bool b);
		CsharpPats.Rectangle getRects();
		void setRects(CsharpPats.Rectangle rect);
	}
}


 

using System;
using CsharpPats;
namespace State
{
	/// <summary>
	/// save the state of a visual rectangle
	/// </summary>
	public class DrawMemento : Memento 	{
		private int x, y, w, h;
		private Rectangle  rect;
		private Drawing visDraw;
		//------
		public DrawMemento(Drawing d) 	{
			 visDraw = d;
			rect = visDraw.getRects ();
			x = rect.x;
			y = rect.y ;  
			w = rect.w;
			h = rect.h;
		}
		//-----
		public void restore() {
			 //restore the state of a drawing object
			rect.x = x;
			rect.y = y;
			rect.h = h;
			rect.w = w;
			visDraw.setRects( rect);
		}
	}
}


 

using System;

namespace State
{
	/// <summary>
	/// Summary description for DrawInstance.
	/// </summary>
	public class DrawInstance :Memento {
		private int intg;
		private Mediator med;
		//-----
		public DrawInstance(int intg, Mediator md) 	{
			this.intg = intg;
			med = md;
		}
		//-----
		public int integ {
			get { return intg;
			}
		}
		//-----
		public void restore() {
			med.removeDrawing(intg);
		}
	}
}


然后是VisRectangle和VisCircle类的设计,VisCircle类继承了VisRectangle类。

using System;
using System.Drawing ;
using CsharpPats;
namespace State
{
	/// <summary>
	/// Summary description for VisRectangle.
	/// </summary>
	public class VisRectangle : Drawing 	{
		protected int x, y, w, h;
		private const int VSIZE=30;
		private const int HSIZE=40;
		private CsharpPats.Rectangle rect;
		protected bool selected;
		protected bool filled;
		protected Pen bPen;
		protected SolidBrush bBrush, rBrush;
		//-----
		public VisRectangle(int xp, int yp) 		{
			x = xp; 			y = yp;
			w = HSIZE;			h = VSIZE;
			saveAsRect();
			bPen = new Pen(Color.Black);
			bBrush = new SolidBrush(Color.Black);
			rBrush = new SolidBrush (Color.Red );
		}
		//-----
		//used by Memento for saving and restoring state
		 public CsharpPats.Rectangle getRects() {
			return rect;
		}
		//-----
		 public void setRects(CsharpPats.Rectangle value) {
				x=value.x;			y=value.y;
				w=value.w;			h=value.h;
				saveAsRect();
		}
		//------
		public void setSelected(bool b) {
			selected = b;
		}
		//-----
		//move to new position
		public void move(int xp, int yp) {
			x = xp;  		y = yp;
			saveAsRect();		
		}
		//-----
		public virtual void draw(Graphics g) { 
			//draw rectangle
			g.DrawRectangle(bPen, x, y, w, h);
			if(filled)
				g.FillRectangle (rBrush, x,y,w,h);
			drawHandles(g);	
		}
		//-----
		public void drawHandles(Graphics g) {
			if (selected) {   //draw handles
				g.FillRectangle(bBrush, x + w / 2, y - 2, 4, 4);
				g.FillRectangle(bBrush, x - 2, y + h / 2, 4, 4);
				g.FillRectangle(bBrush, x + (w / 2), y + h - 2, 4, 4);
				g.FillRectangle(bBrush, x + (w - 2), y + (h / 2), 4, 4);
			}
		}
		//-----
		//return whether point is inside rectangle
		public bool contains(int x, int y) {
			return rect.contains (x, y);
		}
		//------
		//create Rectangle object from new position
		protected void saveAsRect() {
			rect = new CsharpPats.Rectangle (x,y,w,h);
		}
		public void setFill(bool b) {
			filled = b;
		}
	}
}


 

using System;
using System.Drawing ;
namespace State
{
	/// <summary>
	/// Summary description for VisCircle.
	/// </summary>
	public class VisCircle : VisRectangle 	{
		private int r;
		public VisCircle(int x, int y):base(x, y) 		{
			r = 15; w = 30; h = 30;
			saveAsRect();
		}
		//-----
		public override void draw(Graphics g) {
			if (filled) {
				g.FillEllipse(rBrush, x, y, w, h);
			}
        g.DrawEllipse(bPen, x, y, w, h);
        if (selected ){
			drawHandles(g);
			}
	    }
	}
}


状态模式为应用所能拥有的每种状态都创建了一个基础状态对象的子类,并在这些状态之间切换,就如同是应用程序在状态之间做转换。

不需要使用与各种状态相关的一长串的if-else或者switch语句,因为每种状态都已封装在一个类中。

因为没有用来到出指名程序当前状态的变量,这一方法减少了因编程者忘记检测这一状态变量而导致的错误的发生。

可在应用的各个部分之间共享状态对象,比如在一些各自独立的窗口之间,只要这些状态对象没有一个有专门的实例变量就可以了。

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值