top
Loading...
在VB6中用命令行為模式控制GUI動作
命令行為模式(Command Behavior pattern)允許你在表現層(presentation layer)封裝行為,使得采用自己喜歡的方法組織、跟蹤、撤銷和調用這些行為更加容易。

在現實生活中,次序是難以控制的。一旦某種東西處于運動狀態,我們就很難操作這種動作的離散部分。當然,在現實生活中是不可能撤銷某種動作的。但是在編程過程中,次序卻不是難以琢磨的。如果你的計劃是正確的,你就可以定義行動,接著用你喜歡的方式來控制這些行動。實現這種操作的一個非常有用的工具是命令行為(Command Behavior)模式。

命令行為模式是我們可以使用的一種簡單模式。它在行為(action)概念的具體化和撤銷行為方面顯得尤其有益。把行為轉換到對象中也是一條非常有序的途徑,它可以確保每個操作都會集中在實現該操作的一部分代碼上。

在本文中我們將研究命令行為模式的使用方法,你會感覺它比較有趣。我給窗體添加了一個球的圖片,并且實現了表現球的移動過程的命令。每個球命令都被放入棧中,允許你撤銷球的移動,或者重新查看球的移動。在稍微修補一下代碼之后,你可以發現把表現層(GUI)的操作轉換為命令對象使得我們使用多種方式(例如按鈕和菜單)封裝、組織、跟蹤、撤銷和調用操作容易多了。

定義命令(Command)類

實現命令行為的一個普通的途徑是定義一個帶有Do和Undo方法的基類(或接口)。Undo執行與Do操作相反的行為。Do操作是什么樣子都無關緊要,它可以是任何東西("Do"在VB6中是保留字,因此我把"Execute"作為方法的名字)。

我的例子實現了四個移動命令。每個命令從上下左右四個方向中選擇一個方向執行移動操作。每個命令的Undo操作采用相反的方向調用移動操作。很明顯,我并沒有限定兩維的、直線方向。我可以模擬三維的或者三角法(trigonometric)運算規則的基本移動。現在我聚焦于該命令類。

使用公用基類的原因在于代碼可以多形態地(polymorphically)調用Do或Undo操作,而不用關心命令對象的特定實例。列表1顯示了基本的命令和所有四個衍生命令類的實現。由于VB6不支持類繼承,我就使用了接口繼承。

列表1

' ICommand.cls
Public Sub Execute()
End Sub

Public Sub Undo()
End Sub

Public Property Set Form(ByVal Form As Form1)
End Property

' DownCommand.cls
Option Explicit
Implements ICommand

Private FForm As Form1

Private Sub ICommand_Execute()
FForm.MoveDown
End Sub

Private Sub ICommand_Undo()
FForm.MoveUp
End Sub

' LeftCommand.cls
Public Property Set ICommand_Form(ByVal Form As Form1)
Set FForm = Form
End Property

Option Explicit
Implements ICommand

Private FForm As Form1

Private Sub ICommand_Execute()
FForm.MoveLeft
End Sub

Private Sub ICommand_Undo()
FForm.MoveRight
End Sub

Public Property Set ICommand_Form(ByVal Form As Form1)
Set FForm = Form
End Property

' RightCommand.cls
Option Explicit
Implements ICommand

Private FForm As Form1

Private Sub ICommand_Execute()
FForm.MoveRight
End Sub

Private Sub ICommand_Undo()
FForm.MoveLeft
End Sub

Public Property Set ICommand_Form(ByVal Form As Form1)
Set FForm = Form
End Property

' UpCommand.cls
Option Explicit
Implements ICommand

Private FForm As Form1

Private Sub ICommand_Execute()
FForm.MoveUp
End Sub

Private Property Set ICommand_Form(ByVal RHS As Form1)
Set FForm = RHS
End Property

Private Sub ICommand_Undo()
FForm.MoveDown
End Sub

請注意在列表1中ICommand使用了前綴I。這是接口的一個通俗的前綴符號,在多種語言中被廣泛的應用。它的目的是幫助讀者記住該模塊只包含定義。同時還要注意所有的方向命令中都使用了Implements語句。這確保了每個類最少擁有ICommand接口。其結果是我可以定義一個ICommand變量,并給它指定實現了ICommand類的任何實例。

最后我還要指出,每個命令都保持了特定的Form(Form1)的指針。其原因在于Form1包含了自己的邊界和我希望用命令操作的控件的信息。

建立GUI

該程序的GUI由Form1表現,它包含了一個圖像(image)控件,在該控件中有一個球的位圖。該窗體還包含了一個對象集合,我把這個集合作為先前命令的堆棧。通過把每條命令添加到該集合的末尾,以及從集合末尾彈出命令,我可以相反地跟蹤命令的執行過程。從本質上來說,該集合扮演了堆棧的角色。我在命令執行之后壓入(push)命令,彈出(pop)命令調用Undo操作。

為了演示Undo操作,我定義了Edit|Undo菜單和Edit|Rewind操作。Undo從堆棧中彈出一條命令并調用Undo。Rewind彈出所有命令,在每個命令上調用Undo。列表2包含了Form1的代碼。

列表2

Option Explicit

Private stack As Collection
Private Const MOVEMENT_AMOUNT As Integer = 50

Private Sub AboutMenu_Click()
Const About As String = "Command Pattern Demo" + vbCrLf + _
"Copyright (c) 2004. All Rights Reserved" + vbCrLf + _
"Written By Paul Kimmel. [email protected]"

MsgBox About, vbInformation + vbOKOnly, "About"

End Sub

Private Sub ExitMenu_Click()
End
End Sub

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyBack
UndoCommand
Case vbKeyLeft
Call ProcessCommand(New LeftCommand)
Case vbKeyUp
Call ProcessCommand(New UpCommand)
Case vbKeyRight
Call ProcessCommand(New RightCommand)
Case vbKeyDown
Call ProcessCommand(New DownCommand)
End Select
End Sub

Private Function PopStack() As ICommand
On Error GoTo Catch
Debug.Print "Stack count: " & stack.Count
If (stack.Count = 0) Then
Beep
Set PopStack = Nothing
Exit Function
End If

Set PopStack = stack.Item(stack.Count)
Call stack.Remove(stack.Count)
Exit Function
Catch:
Debug.Print Err.Description
Set PopStack = Nothing
End Function

Private Sub PushStack(ByVal command As ICommand)
On Error GoTo Catch
Call stack.Add(command)
Debug.Print "Stack count: " & stack.Count
Exit Sub
Catch:
Debug.Print Err.Description
End Sub

Public Sub UndoCommand()
Dim command As ICommand
Set command = PopStack()
If (command Is Nothing) Then Exit Sub
command.Undo
End Sub

Private Sub ProcessCommand(ByVal command As ICommand)
Set command.Form = Me
command.Execute
Call PushStack(command)
End Sub

Public Sub MoveDown()
Debug.Print "Moving Down"
If (Image1.Top > Height) Then
Image1.Top = 0 - Image1.Height + MOVEMENT_AMOUNT
Else
Image1.Top = Image1.Top + MOVEMENT_AMOUNT
End If
End Sub

Public Sub MoveUp()
Debug.Print "Moving Up"
If (Image1.Top + Image1.Height < 0) Then
Image1.Top = Height - MOVEMENT_AMOUNT
Else
Image1.Top = Image1.Top - MOVEMENT_AMOUNT
End If
End Sub

Public Sub MoveLeft()
Debug.Print "Moving Left"
If (Image1.Left + Image1.Width < 0) Then
Image1.Left = Width - MOVEMENT_AMOUNT
Else
Image1.Left = Image1.Left - MOVEMENT_AMOUNT
End If
End Sub

Public Sub MoveRight()
Debug.Print "Moving Right"
If (Image1.Left > Width) Then
Image1.Left = 0 - Image1.Width + MOVEMENT_AMOUNT
Else
Image1.Left = Image1.Left + MOVEMENT_AMOUNT
End If
End Sub

Private Sub Form_Load()
Set stack = New Collection
End Sub

Private Sub RewindMenu_Click()
While stack.Count > 0
UndoCommand
Wend
End Sub

Private Sub UndoMenu_Click()
Call UndoCommand
End Sub

下一步,我通過把窗體的KeyPreview屬性設置為True并建立與該按鍵相關的命令對象,把有關的方向箭頭按鍵與每條命令關聯起來。例如vbKeyUp應該建立一個UpCommand對象。接著我處理這些命令(我也可以使用工廠創建型模式把命令對象的創建過程移動到命令類的內部)。

ProcessCommand被定義為把Form1的指針賦予某個命令的Form屬性、執行該命令并把該命令壓入集合堆棧中。你沒有必要實現一個堆棧,但是如果你沒有跟蹤命令執行的次序,那么使用Undo操作是不可能的。

圖1顯示的UML模型顯示了示例程序中不同的類之間的關系。


圖1:命令中的類:描述我們的命令模式中關系的UML類圖表

檢查UML中的工作

如果你了解UML,那么你應該知道圖1基本上概括了本文的內容。如果你不了解UML(它是一個很好的技巧),這種簡單的類圖表也不難理解。線(Line)表現了關系。菱形(Diamond)表現了完整的和局部的關系,也就是集合體。完整的關系帶有菱形,而部分的卻沒有。方向鍵表現了聯合關系(associations),它與集合關系大致相當,三角形表現了繼承(inheritance),被繼承的東西帶有三角形。虛線和方向線表明了依賴關系和必要的信息。

請查看圖1,從邏輯上講,LeftCommand、RightCommand、DownCommand和UpCommand都繼承自ICommand。命令與窗體有關聯,但是并不是擁有窗體--這是由它們的Form屬性表現出來的。Form依賴于命令,這是因為它是該Form的操作被調用的方式,并且Form與集合有聚合關系,因為Form負責處理集合的生命周期。

這些定義并非UML中精確的技術。例如,我們可以區分繼承(稱為一般化)和接口繼承(稱為實現),但是說明卻是遵循實用主義的。

回顧簡單的堆棧表現

本文中的技術要求你通曉一些堆棧表現的知識。當我還是一名密歇根州大學(Michigan State University)大學生的時候,我就必須實現堆棧,在那個時候這是很困難的。初學編程的人可以從這兒的堆棧快速回顧中受益,在VB6中很容易使用集合實現堆棧,并且非常有用。
堆棧是一種遵循后進先出(LIFO)規則的數據結構,它受到兩種基本的操作(push和pop)。給堆棧添加內容的時候必須使用push操作,刪除內容的時候使用pop操作。調用push添加的最后的內容在調用pop的時候會被最先取出。例如,如果你有一個整數堆棧,并且壓入了1、2、3、4、5,那么5次調用pop的時候會得到5、4、3、2、1。因此,堆棧是用于向后跟蹤的很好的數據結構,有趣的是,它也是你的CPU跟蹤運行的代碼路徑的基本原理。

但是,集合要成為堆棧僅僅依賴于你插入和刪除內容的位置。通過在集合的末尾插入和刪除內容,設計堆棧只需要少量的代碼。例如,用collection.Add實現push調用,用collection.Remove(collection.Count)實現pop調用。簡單地把集合的尾部作為數據結構的頭部高效地把集合轉換成了簡單的堆棧。你在列表2的PushStack和PopStack方法中可以看到例子。

最后,你還可以對本文的例子進行很好的修改。把集合、PushStack和PopStack移動到一個Stack類可以使窗體更加整潔,并且建立了一個良好的通用堆棧類。采用這種方式改善代碼的設計稱為重構(refactoring)。學習代碼重構是學習模式的良好補充。

工具的品質

在本文中你學習了一種工具--命令行為模式。如果發現被調用的行為需要被撤銷,或者使用的代碼過于復雜,那么我希望你能夠記起這個簡單但是功能強大的工具,其實本文討論的其它一些概念--UML、模式和重構本質上也是工具。

任何任務都不可能有完美的工具(有句諺語是這樣說的"如果你只擁有錘子,那么一切問題都是釘子")。我的父親常說工匠通常是由于他們工具的品質而知名的。換句話說,你所擁有的工具越多,你能實現的功能也就越多。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗