學習過設計模式的人都知道有一種行為模式叫做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對象對立關聯即可。即使以後在用戶界面上增加其它形式的調用,如上下文菜單,或是快捷鍵等,都可以直接與這些請求對象建立關聯。可以很輕松地擴充用戶發出請求的方式。