學習過設計模式的人都知道有一種行為模式叫做Command模式。在Delphi的VCL Framework中也使用到了這種模式,那就是Action模式。
命令模式使用的目的在於使用對象來封裝用戶端的請求命令,由於使用以對象封裝,因此可以達到下面的效果:
- 請求對象可結合多態以及虛擬方法來提供更大的彈性;
- 負責執行請求的目的對象可以和用戶端分離,這就表示多個用戶端可以發生相同的請求對象,例如菜單或是工具列按鈕都可以發生開啟檔案的請求,如此一來菜單和工具列按鈕便可以使用相同的請求對象,而負責開啟檔案的程式碼並不會綁定到單一的功能表項目或是工具列按鈕;
- 由於使用了請求對象,因此不單是圖形化使用者介面可以觸發請求,一般的程式碼也可以通過請求對象來執行特定的工作;
- 由於請求對象可以使用一個完整的類架構來實現,因此可以讓用戶端使用一致的程式碼格式來觸發各種不同的請求。
實現
在Delphi的Classes單元中提供了Action設計模式的實作類別和程式碼。
TBasicAction = class(TComponent)
private
FActionComponent: TComponent;
FOnChange: TNotifyEvent;
FOnExecute: TNotifyEvent;
FOnUpdate: TNotifyEvent;
procedure SetActionComponent(const Value: TComponent);
protected
FClients: TList;
procedure Change; virtual;
procedure SetOnExecute(Value: TNotifyEvent); virtual;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function HandlesTarget(Target: TObject): Boolean; virtual;
procedure UpdateTarget(Target: TObject); virtual;
procedure ExecuteTarget(Target: TObject); virtual;
function Execute: Boolean; dynamic;
procedure RegisterChanges(Value: TBasicActionLink);
procedure UnRegisterChanges(Value: TBasicActionLink);
function Update: Boolean; virtual;
property ActionComponent: TComponent read FActionComponent write SetActionComponent;
property OnExecute: TNotifyEvent read FOnExecute write SetOnExecute;
property OnUpdate: TNotifyEvent read FOnUpdate write FOnUpdate;
end;
...{ TBasicAction }
constructor TBasicAction.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FClients := TList.Create;
end;
destructor TBasicAction.Destroy;
begin
inherited Destroy;
if Assigned(ActionComponent) then
ActionComponent.RemoveFreeNotification(Self);
while FClients.Count > 0 do
UnRegisterChanges(TBasicActionLink(FClients.Last));
FreeAndNil(FClients);
end;
function TBasicAction.HandlesTarget(Target: TObject): Boolean;
begin
Result := False;
end;
procedure TBasicAction.ExecuteTarget(Target: TObject);
begin
end;
procedure TBasicAction.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = ActionComponent) then
FActionComponent := nil;
end;
procedure TBasicAction.UpdateTarget(Target: TObject);
begin
end;
function TBasicAction.Execute: Boolean;
begin
if Assigned(FOnExecute) then
begin
FOnExecute(Self);
Result := True;
end
else Result := False;
end;
function TBasicAction.Update: Boolean;
begin
if Assigned(FOnUpdate) then
begin
FOnUpdate(Self);
Result := True;
end
else Result := False;
end;
procedure TBasicAction.SetOnExecute(Value: TNotifyEvent);
var
I: Integer;
begin
if (TMethod(Value).Code <> TMethod(OnExecute).Code) or
(TMethod(Value).Data <> TMethod(OnExecute).Data) then
begin
for I := 0 to FClients.Count - 1 do
TBasicActionLink(FClients[I]).SetOnExecute(Value);
FOnExecute := Value;
Change;
end;
end;
procedure TBasicAction.Change;
begin
if Assigned(FOnChange) then FOnChange(Self);
end;
procedure TBasicAction.RegisterChanges(Value: TBasicActionLink);
begin
Value.FAction := Self;
FClients.Add(Value);
end;
procedure TBasicAction.UnRegisterChanges(Value: TBasicActionLink);
var
I: Integer;
begin
for I := 0 to FClients.Count - 1 do
if FClients[I] = Value then
begin
Value.FAction := nil;
FClients.Delete(I);
Break;
end;
end;
procedure TBasicAction.SetActionComponent(const Value: TComponent);
begin
if FActionComponent <> Value then
begin
if Assigned(FActionComponent) then
FActionComponent.RemoveFreeNotification(Self);
FActionComponent := Value;
if Assigned(FActionComponent) then
FActionComponent.FreeNotification(Self);
end;
end;
TBasicAction類中聲明了三個關鍵的虛護方法以及一個關鍵的動態方法。
function HandlesTarget(Target: TObject): Boolean; virtual;
procedure UpdateTarget(Target: TObject); virtual;
procedure ExecuteTarget(Target: TObject); virtual;
function Execute: Boolean; dynamic;
其中的動態方法Execute可以由TBasicActionLink類或是TBasicActionLink的衍生類別或是用戶端程式代碼調用,而該方法則會執行程式員在它的OnExecute事件中編寫的事件處理常式。對於TBasicAction的衍生類別而言,例如處理Paste動作的TEditPase類,就可以改寫HandlerTarget虛方法,並且在其中編寫執行粘貼的程式碼。
TEditPaste = class(TEditAction)
public
procedure UpdateTarget(Target: TObject); override;
procedure ExecuteTarget(Target: TObject); override;
end;{ TEditPaste }procedure TEditPaste.ExecuteTarget(Target: TObject);
begin
GetControl(Target).PasteFromClipboard;
end;procedure TEditPaste.UpdateTarget(Target: TObject);
begin
Enabled := Clipboard.HasFormat(CF_TEXT);
end;
因此,當我們要使用Action設計模式時,可以編寫TBasicAction的衍生類別,並且改寫ExecuteTarget虛方法,就像上面提到的TEditPaste類一樣。或是實現企業邏輯程式碼並且把它指定給TBasicAction類的OnExecute事件,然後再調用Execute虛方法。
我們通過繼承TBasicAction類實現了請求對象類,那麼如何將用戶端與這些請求對象建立關聯呢?這裡就用到了TBasicActionLink。
TBasicActionLink = class(TObject)
private
FOnChange: TNotifyEvent;
protected
FAction: TBasicAction;
procedure AssignClient(AClient: TObject); virtual;
procedure Change; virtual;
function IsOnExecuteLinked: Boolean; virtual;
procedure SetAction(Value: TBasicAction); virtual;
procedure SetOnExecute(Value: TNotifyEvent); virtual;
public
constructor Create(AClient: TObject); virtual;
destructor Destroy; override;
function Execute(AComponent: TComponent = nil): Boolean; virtual;
function Update: Boolean; virtual;
property Action: TBasicAction read FAction write SetAction;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
...{ TBasicActionLink }
constructor TBasicActionLink.Create(AClient: TObject);
begin
inherited Create;
AssignClient(AClient);
end;
procedure TBasicActionLink.AssignClient(AClient: TObject);
begin
end;
destructor TBasicActionLink.Destroy;
begin
if FAction <> nil then FAction.UnRegisterChanges(Self);
inherited Destroy;
end;
procedure TBasicActionLink.Change;
begin
if Assigned(OnChange) then OnChange(FAction);
end;
function TBasicActionLink.Execute(AComponent: TComponent): Boolean;
begin
FAction.ActionComponent := AComponent;
Result := FAction.Execute;
end;
procedure TBasicActionLink.SetAction(Value: TBasicAction);
begin
if Value <> FAction then
begin
if FAction <> nil then FAction.UnRegisterChanges(Self);
FAction := Value;
if Value <> nil then Value.RegisterChanges(Self);
end;
end;
function TBasicActionLink.IsOnExecuteLinked: Boolean;
begin
Result := True;
end;
procedure TBasicActionLink.SetOnExecute(Value: TNotifyEvent);
begin
end;
function TBasicActionLink.Update: Boolean;
begin
Result := FAction.Update;
end;
在上面的代碼中我們可以看到TBasicActionLink的Execute方法實際上也就是調用了TBasicAction對象的虛方法Execute來負責響應用戶端的請求。
應用舉例
在通常的UI設計中,我們會在Form上放置一些功能表項目,同時會把部分使用頻率較高的功能以工具列形式提供給使用者。這些工具列按鈕實現的功能與功能表項目完全相同,我們就可以使用Action模式來設計這些請求,然後將功能表項目和工具列按鈕與這些Action對象對立關聯即可。即使以後在使用者介面上增加其它形式的調用,如操作功能表,或是快速鍵等,都可以直接與這些請求對象建立關聯。可以很輕鬆地擴充使用者發出請求的方式。