模塊乙:
procedure TModule2.acAction1Execute(Sender: TObject);
begin
……
Flog.LogCommand(“模塊乙 操作一”);
end;
就這樣,寫了大約二十幾個地方,突然覺得自己太可悲了,作為一個高科技人才就干這種體力活嗎?
壞味道的出現
在許許多多的地方都出現了Flog.LogCommand這樣的函數調用,正是這些函數調用讓我崩潰,在這麼做下去估計我撐不到周末了。
“CV方法”已經讓我覺得羞愧加惱怒,系統中到處出現了這樣的重復代碼。
無奈之中,我耷拉著腦袋走到一個同事桌前。
“嘿,救救我吧,我想解脫”
“怎麼回事?”同事善意地問道。
“事情是這樣子的……”
通過了一番討論,我們一致認為這個應該用AOP的思想來解決。但怎樣在Delphi中來實現AOP呢,修改整個程序框架是不可能的,我們只能在現有的基礎上做。
正當我們要放棄的時候,突然想到了一個突破點:日志中記錄的功能在程序實現的時候全部使用Action組件來做的,是否可以考慮在Action上面做文章呢?
曙光啊,曙光!
解決方式——瞞天過海
通俗點理解AOP,就是將一段代碼統一“插入”某一類地方。但像Delphi這樣的語言是很難實現“插入”代碼的這一功能。不過我們可以通過事件機制來實現同樣的效果。
Action的執行代碼都寫在事件OnExecute中,如果能在執行事件之前和之後執行我想要的動作是不是就可以解決了?
procedure TModule1.acAction1Execute(Sender: TObject);
begin
// do something
end;
procedure TActionHook.RegisterAction(Action: TAction);
begin
// 記錄Action與原始的OnExecute事件
FActionList.Add(Action);
SetLength(FActionEvents, Length(FActionEvents) + 1);
FActionEvents[High(FActionEvents)] := Action.OnExecute;
// 瞞天過海,偷換事件
Action.OnExecute := HookActionExecute;
end;
procedure TActionHook.HookActionExecute(ASender: TObject);
begin
DoBeforeActionExecute(TAction(ASender));
// 觸發原始事件
FActionEvents[FActionList.IndexOf(ASender)](ASender);
DoAfterActionExecute(TAction(ASender));
end;
procedure TActionHook.DoAfterActionExecute(Action: TAction);
begin
// 所有的Action執行完畢後調用此處
FLog.LogCommand(Action.Caption);
end;
相關的UML圖如下:
采用這樣的方式後,很明顯我們不需要將日志相關代碼分散到系統的各個地方,只需要在一個統一的地方將所有Form上的Action組件注冊到TActionHook中就可以了。
擴展思考
在Delphi中可以通過事件的機制實現代碼注入技術,當然同樣在其他支持事件的語言中也可以實現。相比之下這種方法實現AOP比較簡單,並且不需要在系統的整體結構上作什麼調整,完全通過語言層面支持。
例子中針對Action的組件來處理日志功能,將TActionHook擴展之後可以將其他的控件操作也通過這套機制記錄到日志中。
很多同行們都埋怨自己做的是體力活,沒什麼技術含量。同樣在剛開始的時候,我也認為這個任務是體力活,但是如果我們能勤於思考新的解決方法,體力活絕對能夠變為技術活。只有這樣才能不辜負“高科技”這個美譽啊。
上面介紹的方法肯定不是最好的,這次拿出來和大家分享,一方面是將自己的經驗獻給需要的朋友,另外也特別希望大家能給一點好的建議,一起交流,共同學習。