AOP 剖面導向程式設計初試身手

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 功能的 AOP

若是需要再加上 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 這本書的第十章內容,寫的相當精采且詳細