1、Dephi 状态模式(State 模式)作者 陈省对于面向对象开发来说,很长的函数和过程是不容易维护的,同样冗长的 if else 以及 case of 的条件判断语句会使得代码不够清晰,特别是当条件判断很多时,比如有几十条甚至十几条条件判断语句,会使得代码难以修改和维护。在早期 windows 开发中,程序员经常需要编写巨大的消息判断语句,根据消息的不同调用不同的消息处理函数,后来为了改善消息处理的复杂程度,Delphi引入了 message 关键字,从编译器一级对消息处理进行了映射,减少了条件判断语句的大量使用,这一案例就是巨大的条件判断语句弊端的典型体现。通常说来,if else 以及
2、case 语句都是对对象的某个状态或者属性进行判断,根据对象的状态或属性的不同,执行不同的操作。实际上就是一个有限状态机,为了消除这些 if else 条件判断语句,我们可以使用 State 模式来解决。所谓状态模式就是将宿主对象中每一种可能的状态抽象成一个状态类,当宿主对象的状态发生变化时,宿主对象改变自己的状态,并执行不同状态类对应的不同操作。以圣斗士星矢为例,当他的小宇宙处于不同的状态时,他发出的天马流星拳、天狗流星拳、天鹅流星拳具有不同的威力,分别对应于青铜圣斗士,白银圣斗士,黄金圣斗士的级别。对星矢进行建模后的 UML 图示意如下:比较一下使用 State 模式前后的不同,没有使用
3、State 模式前,星矢使用天马流星拳等招数的伪代码:procedure 星矢.天马流星拳;begincase 小宇宙 of黄金圣斗士: 打出黄金圣斗士威力的天马流星拳白银圣斗士:打出白银圣斗士威力的天马流星拳青铜圣斗士:打出青铜圣斗士威力的天马流星拳end;end;procedure 星矢.天狗流星拳;begincase 小宇宙 of黄金圣斗士: 打出黄金圣斗士威力的天狗流星拳白银圣斗士:打出白银圣斗士威力的天狗流星拳青铜圣斗士:打出青铜圣斗士威力的天狗流星拳end;end;procedure 星矢.天鹅流星拳;begincase 小宇宙 of黄金圣斗士: 打出黄金圣斗士威力的天鹅流星拳白银
4、圣斗士:打出白银圣斗士威力的天鹅流星拳青铜圣斗士:打出青铜圣斗士威力的天鹅流星拳end;end;使用 State 模式后,星矢使用天马流星拳等招数的伪代码示意如下:/设定小宇宙状态星矢.小宇宙:=黄金圣斗士;procedure 星矢.天马流星拳;begin小宇宙.天马流星拳end;procedure 星矢.天狗流星拳;begin小宇宙.天狗流星拳end;procedure 星矢.天鹅流星拳;begin小宇宙.天鹅流星拳end;可以看到宿主对象的代码简化了许多,同时消除了 case 条件判断语句,同时使用 State 模式的好处还在于便于扩充和修改,比如要调整黄金圣斗士的天马流星拳的威力,只要修
5、改黄金圣斗士小宇宙对象的相应方法就可以了,不用象以前那样在一个 case 语句中寻找相应的状态对应的代码。同时便于扩充,比如客户觉得有必要给星矢增加一个小宇宙状态,比如超级塞亚人小宇宙,在不改动宿主对象代码的情况,只要添加一个新类就可以了,宿主对象的所有同小宇宙相关的招数方法都无须改变,而使用 case 条件判断语句的话,则需要在每一个招数方法中添加新的判断,如果星矢会的招数太多的话,修改的地方就是大量的了。虽然 State 模式有上面的优点,但是它也有它的缺点,就是如果宿主对象的状态比较多的话,会产生大量的小粒度的对象,显得对象过多,体系不紧凑。另外,State 模式的模型图和策略模式的模型
6、图几乎一样,区别在于状态模式的特点是状态会经常发生变化,而策略模式一般来说在使用的时候通常是固定的,不会频繁变化。另外两个模式的目的也不同,对状态改变时,对象具有不同的行为进行抽象,对应于 State 模式。而策略模式则是为了解决算法的互换问题的。状态模式使用示例在 Delphi 的demosdocgraphex 目录下,提供了一个画图程序的例子,运行后可以根据当前绘图工具的不同,绘制不同的形状,示意图如下:其中核心的绘图部分就是一个大的添加判断过程 DrawShape,根据当前绘图工具的不同(线、圆、矩形、圆角矩形),绘制不同的形状,代码如下:procedure TForm1.DrawSha
7、pe(TopLeft, BottomRight: TPoint; AMode: TPenMode);beginwith Image.Canvas dobeginPen.Mode := AMode;case DrawingTool ofdtLine:beginImage.Canvas.MoveTo(TopLeft.X, TopLeft.Y);Image.Canvas.LineTo(BottomRight.X, BottomRight.Y);end;dtRectangle: Image.Canvas.Rectangle(TopLeft.X, TopLeft.Y, BottomRight.X,Bot
8、tomRight.Y);dtEllipse: Image.Canvas.Ellipse(Topleft.X, TopLeft.Y, BottomRight.X,BottomRight.Y);dtRoundRect: Image.Canvas.RoundRect(TopLeft.X, TopLeft.Y, BottomRight.X,BottomRight.Y, (TopLeft.X - BottomRight.X) div 2,(TopLeft.Y - BottomRight.Y) div 2);end;end;end;下面我们就对这一例子使用 State 模式进行改造,消除 Case 判断。
9、首先对系统建模:修改后的代码如下:typeTDrawingTool = class(TObject)privateFImage: TImage;publicconstructor Create(AImage: TImage);procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode); virtual;abstract;end;TLineTool = class(TDrawingTool)procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);
10、override;end;TRectTool = class(TDrawingTool)procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);override;end;TRoundRectTool = class(TDrawingTool)procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);override;end;TEllipseTool = class(TDrawingTool)procedure DrawShape(TopLef
11、t, BottomRight: TPoint; AMode: TPenMode);override;end;TForm1 = class(TForm)privateFDrawingTool: TDrawingTool;procedure SetDrawingTool(const Value: TDrawingTool); Private declarations public Public declarations procedure DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);property DrawingTool: T
12、DrawingTool read FDrawingTool write SetDrawingTool;end;procedure TForm1.LineButtonClick(Sender: TObject);beginFreeAndNil(FDrawingTool);DrawingTool := TLineTool.Create(Image);end;procedure TForm1.RectangleButtonClick(Sender: TObject);beginFreeAndNil(FDrawingTool);DrawingTool := TRectTool.Create(Image
13、);end;procedure TForm1.EllipseButtonClick(Sender: TObject);beginFreeAndNil(FDrawingTool);DrawingTool := TEllipseTool.Create(Image);end;procedure TForm1.RoundRectButtonClick(Sender: TObject);beginFreeAndNil(FDrawingTool);DrawingTool := TRoundRectTool.Create(Image);end;procedure TForm1.DrawShape(TopLe
14、ft, BottomRight: TPoint; AMode: TPenMode);beginDrawingTool.DrawShape(TopLeft, BottomRight, AMode);end;procedure TForm1.FormCreate(Sender: TObject);beginLineButton.OnClick(nil);end; TEllipseTool procedure TEllipseTool.DrawShape(TopLeft, BottomRight: TPoint;AMode: TPenMode);beginwith FImage.Canvas dob
15、eginPen.Mode := AMode;Ellipse(Topleft.X, TopLeft.Y, BottomRight.X,BottomRight.Y);end;end; TRoundRectTool procedure TRoundRectTool.DrawShape(TopLeft, BottomRight: TPoint;AMode: TPenMode);beginwith FImage.Canvas dobeginPen.Mode := AMode;RoundRect(TopLeft.X, TopLeft.Y, BottomRight.X,BottomRight.Y, (Top
16、Left.X - BottomRight.X) div 2,(TopLeft.Y - BottomRight.Y) div 2);end;end; TRectTool procedure TRectTool.DrawShape(TopLeft, BottomRight: TPoint;AMode: TPenMode);beginwith FImage.Canvas dobeginPen.Mode := AMode;Rectangle(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);end;end; TLineTool procedure
17、TLineTool.DrawShape(TopLeft, BottomRight: TPoint;AMode: TPenMode);beginwith FImage.Canvas dobeginPen.Mode := AMode;MoveTo(TopLeft.X, TopLeft.Y);LineTo(BottomRight.X, BottomRight.Y);end;end;procedure TForm1.SetDrawingTool(const Value: TDrawingTool);beginFDrawingTool := Value;end; TDrawingTool constructor TDrawingTool.Create(AImage: TImage);beginFImage := AImage;end;end.