AOP(Aspect-oriented programming) 意思為剖面導向程式設計,主是要是把非商業邏輯且重複要做的事情分割成一個剖面,而這個剖面是可以重複套用到你的核心程式上
把非商業邏輯且重複要做的事情分割成一個剖面,進而選擇套用
而這樣說可能還是不知道 AOP 到底能幫上什麼忙
先來看個例子,有一個 OrderService, 裡面有 update, delete 兩個方法
public class OrderService
{
public void Update(string orderId)
{
using (var tran = new TransactionScope())
{
try
{
//update order info
tran.Complete();
}
catch (Exception ex)
{
}
}
}
public void Delete(string orderId)
{
using (var tran = new TransactionScope())
{
try
{
//delete order info
tran.Complete();
}
catch (Exception ex)
{
}
}
}
}
這樣的程式碼也許你不陌生,甚至一直以來你都是這麼做的
你應該有發現 TransactionScope 會重複地出現
只要在有呼叫資料庫做變更資料的動作時,都會套用,而只有 query 操作時不會使用到
如果說 TransactionScope 可以抽出來,並重複使用,你相信嗎?
AOP 即是在處理這樣的事情
如果用 AOP 的方式來寫,你的程式碼會更漂亮
在這邊,我們可以把 TransactionScope 當成一個剖面
讓 Update, Delete 只做商業邏輯所要做的事就好,也就是 update/delete data 就好,不需要去管 transaction 的事情
要達到這樣的 AOP 的目地,有現成的工具可以使用,在之後的篇章會介紹一些工具使用
在這使用的是不要依賴工具進行 AOP 的改造
先把這個 service 裡的所有 method 各自拆開,並以泛型建立 interface
並把 Transction 的功能拿掉,讓所屬的服務只做他們該做的事,以達到 SRP, OCP 的目地
public class UpdateOrderService<T> : IOrderCommandService<T>
{
public void Execute(T order)
{
//update order here
}
}
public class DeleteOrderService<T>: IOrderCommandService<T>
{
public void Execute(T order)
{
//delete order here
}
}
最後是 interface
public interface IOrderCommandService<T>
{
void Execute(T order);
}
接著就要利用裝飾者模飾或是代理模式來達到 AOP 的效用,接著就得需要建立一個裝飾者來攔截動作
在 TransactionCommandServiceDecorator 裡讓它就只做 Transaction 的橫切面職責
在執行之前先攔截並創建一個 TransactionScope
public class TransactionCommandServiceDecorator<T>: IOrderCommandService<T>
{
private readonly IOrderCommandService<T> _decoratee;
public TransactionCommandServiceDecorator(IOrderCommandService<T> decoratee)
{
_decoratee = decoratee;
}
public void Execute(T order)
{
using (var tran = new TransactionScope())
{
try
{
_decoratee.Execute(order);
tran.Complete();
}
catch (Exception ex)
{
}
}
}
}
在程式開始,或是組合根裡,即可這樣產生實體
IOrderCommandService<string> updateService = new UpdateOrderService<string>();
IOrderCommandService<string> deleteService = new DeleteOrderService<string>();
IOrderCommandService<string> tranUpdateService = new TransactionCommandServiceDecorator<string>(updateService);
IOrderCommandService<string> tranDeleteService = new TransactionCommandServiceDecorator<string>(deleteService);
tranUpdateService.Execute("123");
tranDeleteService.Execute("123");
若是需要再加上 log 的功能,修改的方式會變的更簡單
只需加上 LogCommandServiceDecorator 的新類別
public class LogCommandServiceDecorator<T> : IOrderCommandService<T>
{
private readonly IOrderCommandService<T> _decoratee;
public LogCommandServiceDecorator(IOrderCommandService<T> decoratee)
{
_decoratee = decoratee;
}
public void Execute(T order)
{
//log here
try
{
_decoratee.Execute(order);
}
catch (Exception ex)
{
//log error here
}
}
}
接著再從組合根進行包裝即可,如果我們只想要對 delete order 先做 log 記錄的動作,再做 transaction 的動作,而 update 不需 log ,我們可以這樣組合包裝
IOrderCommandService<string> updateService = new UpdateOrderService<string>();
IOrderCommandService<string> deleteService = new DeleteOrderService<string>();
IOrderCommandService<string> tranUpdateService = new TransactionCommandServiceDecorator<string>(updateService);
//只需再往 tranDeleteService 包上 log 即可
IOrderCommandService<string> tranDeleteService = new TransactionCommandServiceDecorator<string>(deleteService);
IOrderCommandService<string> LogDeleteService = new LogCommandServiceDecorator<string>(tranDeleteService);
tranUpdateService.Execute("123");
LogDeleteService.Execute("123");
但若不透過工具直接進行 AOP 的修改,一般而言對於既有的程式碼是有挑戰的,且有一段路要進行修改,光是設計與架構可能就會花相當多的時間調整
接下來就會介紹兩種不同形式的 AOP 工具,分別是動態攔截(Dynamic Interception) 與 後期織入(Compile-Time Weaving),以便快速達到 AOP 的效果
若想要了解的更清楚的話,可以看 Dependency Injection Principles, Practices, and Patterns 這本書的第十章內容,寫的相當精采且詳細