第五章 面向方面編程___AOP入門


  上一篇講了 AOP 和 OOP 的區別,這一次我們開始入門 AOP 。實現面向方面編程的技術,主要分為兩大類:

一是 采用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;

二是 采用靜態織入的方式,引入特定的語法創建 “方面”,從而使得編譯器可以在編譯期間織入有關 “方面” 的代碼。

然而殊途同歸,實現 AOP 的技術特性卻是相同的,分別為:

crosscutting concerns (橫切性關注點):一個關注點(concern)就是一個特定的目的,一塊我們要完成的區域,一段我們需要的邏輯行為。從技術的角度來說,一個典型的軟件系統包含一些核心的關注點和系統級的關注點。舉個例子來說,一個銀行支付系統的核心關注點是存入/支付處理,而系統級的關注點則是日志、事務完整性、權限、安全及性能問題等,許多關注點(即橫切關注點)會在很多個模塊中出現。如果使用現有的編程方法,橫切關注點會橫越多個模塊,結果是使系統變得越來越復雜,難以設計和實現。通過面向切面編程的方式能夠更好地分離系統關注點,從而提供模塊化的橫切關注點。

aspect(切面):橫切性關注點的抽象即為切面,與類相似,只是兩者的關注點不一樣。類是對物體特征的抽象,切面是對橫切性關注點的抽象。

join point(連接點):所謂連接點就是指那些些被攔截到的點。在Spring中這些連接點指的就是方法,因為在Spring中只支持方法類型的連接點。實際上連接點還可以是構造函數或者字段。通俗的說是程序執行中的一個精確執行點,例如類中的一個方法。它是一個抽象的概念,在實現面向切面編程時,並不需要去定義一個join point。

point cut(切入點):切入點就是指我們要對那些方法進行攔截和定義。

advice(通知):通知就是指我們攔截到方法之后,要做的事情。Spring中有前置通知,后置通知,異常通知,最終通知,環繞通知。

target object(目標對象):指包含連接點的對象。也稱為被通知或被代理對象。

weave(織入):將切面應用到目標對象,並導致代理對象創建的過程叫做織入

Introduction(引入):運行期間,在不修改代碼的情況下,動態的為類添加方法和字段。通俗的將就是為對象引入附加的方法或屬性。

 

使用代理模式實現面向切面編程

  下面我們使用代理模式來模擬一下,現面向切面編程。通過上面那幅圖,我們看到使用面向切面編程將核心業務和其他業務(日志記錄,性能檢測等)分離開來。這次我們模擬一下,銀行支付中記錄日志的問題。

我們有 兩個接口,ICard,ILogger , ICard 表示 卡的接口,ILogger 表示 日志記錄接口。

卡接口:

 1 namespace CnblogLesson_5_1.Interface
 2 {
 3     /// <summary>
 4     /// 5     /// </summary>
 6     interface ICard
 7     {
 8         //存入
 9         void Deposit(double money);
10 
11         //支出
12         void Pay(double money);
13     }
14 }

日志接口:

 1 namespace CnblogLesson_5_1.Interface
 2 {
 3     //日志
 4     interface ILogger
 5     {
 6         /// <summary>
 7         /// 寫入日志
 8         /// </summary>
 9         void LogWrite(string message);
10     }
11 }

卡的實現:

 1 using System;
 2 using CnblogLesson_5_1.Interface;
 3 
 4 namespace CnblogLesson_5_1.Impl
 5 {
 6     class Card : ICard
 7     {
 8         //存入
 9         public void Deposit(double money)
10         {
11             Console.WriteLine("存入:{0}" ,money);
12             Logger log = new Logger();
13             log.LogWrite("日志:" + DateTime.Now.ToShortTimeString() +"存入" + money.ToString());
14         }
15 
16         //支出
17         public void Pay(double money)
18         {
19             Console.WriteLine("支出:{0}", money);
20             Logger log = new Logger();
21             log.LogWrite("日志:" + DateTime.Now.ToShortTimeString() + "支出" + money.ToString());
22         }
23     }
24 }

日志的實現:

 1 using System;
 2 using CnblogLesson_5_1.Interface;
 3 
 4 namespace CnblogLesson_5_1.Impl
 5 {
 6     class Logger : ILogger
 7     {
 8         public void LogWrite(string message)
 9         {
10             Console.WriteLine("寫入到SQL Server 數據庫中:" + message);
11         }
12     }
13 }

調用:

 1 using System;
 2 using CnblogLesson_5_1.Interface;
 3 using CnblogLesson_5_1.Impl;
 4 
 5 namespace CnblogLesson_5_1
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             ICard card = new Card();
12 
13             card.Deposit(100);
14             card.Pay(100);
15 
16             Console.ReadKey();
17 
18         }
19     }
20 }

輸出結果:

 
以上方式的缺陷:
  我們的核心業務(存入/取錢)與記錄日志本不該彼此糾纏在一起的責任卻糾纏在一起,增加了我們系統的復雜性。
 
下面使用代理模式模擬 AOP 實現 核心業務與日志記錄的解耦:
 
ICard 和 ILogger 接口,還是那些接口,日志的實現還是日志的實現,沒有做改動。
現在,存款和取款的 實現只做自己的業務,無需進行日志記錄:
 1 using System;
 2 using 代理模式模擬AOP.Interface;
 3 
 4 namespace 代理模式模擬AOP.Impl
 5 {
 6     class Card : ICard
 7     {
 8         //存入
 9         public void Deposit(double money)
10         {
11             Console.WriteLine("存入:{0}" ,money);
12         }
13 
14         //支出
15         public void Pay(double money)
16         {
17             Console.WriteLine("支出:{0}", money);
18         }
19     }
20 }

現在增加代理類 ProxyCard:

 1 using System;
 2 using 代理模式模擬AOP.Interface;
 3 using 代理模式模擬AOP.Impl;
 4 
 5 namespace 代理模式模擬AOP.Proxy
 6 {
 7     public class ProxyCard
 8     {
 9         private ICard target;
10 
11         public ProxyCard(ICard target)
12         {
13             this.target = target;
14         }
15 
16         public void Invoke(string method, object[] parameters)
17         {
18             if (target != null)
19             {
20                 ILogger log = new  Logger();
21                 double money = double.Parse(parameters[0].ToString());
22                 switch (method) {
23                     case "Pay":
24                         log.LogWrite("日志:" + DateTime.Now.ToShortTimeString() + "支出" + money.ToString());
25                         break;
26                     case "Deposit":
27                         log.LogWrite("日志:" + DateTime.Now.ToShortTimeString() + "存入" + money.ToString());
28                         break;
29                 }
30                 Type type = target.GetType();
31                 type.GetMethod(method).Invoke(target, parameters);
32             }
33         }
34     }
35 
36 }

調用:

 1 using System;
 2 using 代理模式模擬AOP.Interface;
 3 using 代理模式模擬AOP.Impl;
 4 using 代理模式模擬AOP.Proxy;
 5 
 6 namespace 代理模式模擬AOP
 7 {
 8     class Program
 9     {
10         static void Main(string[] args)
11         {
12             ICard card = new Card();
13 
14             ProxyCard proxy = new ProxyCard(card);
15             proxy.Invoke("Pay", new object[] { 100 });
16             proxy.Invoke("Deposit", new object[] { 100 });
17 
18             Console.ReadKey();
19 
20         }
21     }
22 }

執行結果:

 
通過 ProxyCard 代理對象,我們實現了對方法的攔截,在調用之前進行日志記錄的操作。實現了我們的核心業務(存入/支出)與日志記錄的分離,從而降低了系統的耦合。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM