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