上一篇講了 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 }
輸出結果:
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 }
執行結果: