您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關C#中AOP編程思想是什么的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
AOP:Aspect Oriented Programming的縮寫,意為面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP思想的延續。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
為什么要學習AOP呢?
AOP的應用場景非常廣泛,在一些高級工程師或者架構師的面試過程中,頻率出現的比較多。
POP:Procedure Oriented Programming的縮寫,即面向過程編程,是一種以過程為中心的編程思想。
面向過程是分析出解決問題的步驟,然后用函數或者方法,把這些步驟一步一步的實現,使用的時候在一個一個的一次調用函數或者方法,這就是面向過程編程。最開始的時候都是面向過程編程。面向過程是最為實際的一種思考方式。就算是面向對象編程,里面也是包含有面向過程的編程思想,因為面向過程是一種基礎的編程思考方式,它從實際出發來考慮如何實現需求。
POP的不足:面向過程編程,只能處理一些簡單的問題,無法處理一些復雜的問題。如果問題很復雜,全部以流程來思考的話,會發現流程很混亂,甚至流程都不能進行下去。
OOP:Object Oriented Programming的縮寫,即面向對象編程。
早期的計算機編程都是面向過程的,因為早期的編程都比較簡單。但是隨著時間的發展,需求處理的問題越來越多,問題就會越來越復雜,這時就不能簡單的使用面向過程的編程了,就出現了面向對象編程。在計算機里面,把所有的東西都想象成一種事物。現實世界中的對象都有一些屬性和行為,也就對應計算機中的屬性和方法。
面向對象編程就是把構成問題的事物分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描述某個事物在整個解決問題的步驟中的行為。
我們以一棟大樓為例來說明OOP的不足。
我們把系統比喻成一棟樓,類或者對象是磚塊,磚塊組成了一面墻,多面墻構成一間房間,多個房間構成一棟樓。
這就好比一個模塊的功能是由多個類實現,模塊又組成了某一項服務,多個服務構成了一個完整的系統。一個系統開發完成并不代表就真的完成了,以后肯定會有各種需求的變更出現,需求變更出現以后就要去修改代碼,代碼都在類里面,這就相當于去修改類。如果是小范圍的修改影響還不是很大,如果是大范圍的修改,影響就比較大了。即使每次修改都很小,但是如果經常進行修改,影響也會很大。就會造成系統的不穩定。我們得出結論:類應該是固定的,不應該頻繁的去修改,甚至是不允許修改。這也是為什么有那么多的設計原則和設計模式。大部分的設計模式都是為了解決這類問題的,即在不修改類的前提下去擴展功能。
OOP的不足:產生新的需求會導致程序代碼不斷的進行修改,容易造成程序的不穩定。
如果非常了解OOP的話,那么我們應該知道,從對象的組織角度來講,分類方法都是以繼承關系為主線的,我們稱為縱向。如果只使用OOP思想的話,會帶來兩個問題:
1、共性問題。
2、擴展問題,需要對先有類進行擴展時就比較困難了。
OOP與POP的區別:
在對比面向過程的時候,面向對象的方法是把事物最小化為對象,包括屬性和方法。當程序的規模比較小的時候,面向過程編程還是有一些優勢的,因為這時候程序的流程是比較容易梳理清楚的。以早上去上班為例,過程就是起床、穿衣、刷牙洗臉、去公司。每一步都是按照順序完成的,我們只需要按照步驟去一步一步的實現里面的方法就行了,最后在依次調用實現的方法即可,這就是面向過程開發。
如果使用面向對象編程,我們就需要抽象出來一個員工類,該員工具有起床、穿衣、刷牙洗臉、去公司的四個方法。但是,最終要實現早上去上班的這個需求的話,還是要按照順序依次來調用四個方法。最開始的時候,我們是按照面向過程的思想來思考該需求,然后在按照面向對象的思想來抽象出幾個方法,最終要實現這個需求,還是要按照面向過程的順序來實現。
面向對象和面向過程的區別僅僅是在思考問題方式上面的不同。最終你會發現,在你實現這個需求的時候,即使使用了面向對象的思想抽象出來了員工類,但是最后還是要使用面向過程來實現這個需求。
AOP:Aspect Oriented Programming的縮寫,即面向切面編程。是對OOP的一種補充,在不修改原始類的情況下,給程序動態添加統一功能的一種技術。
OOP關注的是將需求功能劃分為不同的并且相對獨立、封裝良好的類,依靠繼承和多態來定義彼此的關系。AOP能夠將通用需求功能從不相關的類中分離出來,很多類共享一個行為,一旦發生變化,不需要去修改很多類,只需要去修改這一個類即可。
AOP中的切面是指什么呢?切面指的是橫切關注點。看下面一張圖:
OOP是為了將狀態和行為進行模塊化。上圖是一個商場系統,我們使用OOP將該系統縱向分為訂單管理、商品管理、庫存管理模塊。在該系統里面,我們要進行授權驗證。像訂單、商品、庫存都是業務邏輯功能,但是這三個模塊都需要一些共有的功能,比如說授權驗證、日志記錄等。我們不可能在每個模塊里面都去寫授權驗證,而且授權驗證也不屬于具體的業務,它其實屬于功能性模塊,并且會橫跨多個業務模塊。可以看到這里是橫向的,這就是所謂的切面。通俗的將,AOP就是將公用的功能給提取出來,如果以后這些公用的功能發生了變化,我們只需要修改這些公用功能的代碼即可,其它的地方就不需要去更改了。所謂的切面,就是只關注通用功能,而不關注業務邏輯,而且不修改原有的類。
AOP優勢:
將通用功能從業務邏輯中抽離出來,提高代碼復用性,有利于后期的維護和擴展。
軟件設計時,抽出通用功能(切面),有利于軟件設計的模塊化,降低軟件架構的復雜度。
AOP的劣勢:
AOP的對OOP思想的一種補充,它無法單獨存在。如果說單獨使用AOP去設計一套系統是不可能的。在設計系統的時候,如果系統比較簡單,那么可以只使用POP或者OOP來設計。如果系統很復雜,就需要使用AOP思想。首先要使用POP來梳理整個業務流程,然后根據POP的流程,去整理類和模塊,最后在使用AOP來抽取通用功能。
AOP和OOP的區別:
面向目標不同:OOP是面向名詞領域(抽象出來一個事物,比如學生、員工,這些都是名詞)。AOP是面向動詞領域(比如鑒權、記錄日志,這些都是動作或行為)。
思想結構不同:OOP是縱向的(以繼承為主線,所以是縱向的)。AOP是橫向的。
注重方面不同:OOP是注重業務邏輯單元的劃分,AOP偏重業務處理過程中的某個步驟或階段。
POP、OOP、AOP三種思想是相互補充的。在一個系統的開發過程中,這三種編程思想是不可或缺的。
我們在上面講解了有關AOP的一些理論知識,那么如何在代碼里面實現呢?
實現AOP有兩種方式:
靜態代理實現。所謂靜態代理,就是我們自己來寫代理對象。
動態代理實現。所謂動態代理,就是在程序運行時,去生成一個代理對象。
實現靜態代理需要使用到兩種設計模式:裝飾器模式和代理模式。
裝飾器模式:允許向一個現有的對象添加新的功能,同時又不改變這個現有對象的結構。屬于結構型設計模式,它是作為現有類的一種包裝。首先會創建一個裝飾類,用來包裝原有的類,并在保持類的完整性的前提下,提供額外的功能。看下面的例子。
我們首先創建一個User類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StaticDemo.Model { public class User { public string Name { get; set; } public string Password { get; set; } } }
接著我們創建一個賬號服務的接口,里面有一個方法,用來注冊一個用戶:
using StaticDemo.Model; namespace StaticDemo.Services { /// <summary> /// 接口 /// </summary> public interface IAccountService { /// <summary> /// 注冊用戶 /// </summary> /// <param name="user"></param> void Reg(User user); } }
然后創建一個類來實現上面的接口:
using StaticDemo.Model; using System; namespace StaticDemo.Services { /// <summary> /// 實現IAccountService接口 /// </summary> public class AccountService : IAccountService { public void Reg(User user) { // 業務代碼 之前 或者之后執行一些其它的邏輯 Console.WriteLine($"{user.Name}注冊成功"); } } }
我們在創建一個裝飾器類:
using StaticDemo.Model; using StaticDemo.Services; using System; namespace StaticDemo { /// <summary> /// 裝飾器類 /// </summary> public class AccountDecorator : IAccountService { private readonly IAccountService _accountService; public AccountDecorator(IAccountService accountService) { _accountService = accountService; } public void Reg(User user) { Before(); // 這里調用注冊的方法,原有類里面的邏輯不會改變 // 在邏輯前面和后面分別添加其他邏輯 _accountService.Reg(user); After(); } private void Before() { Console.WriteLine("注冊之前的邏輯"); } private void After() { Console.WriteLine("注冊之后的邏輯"); } } }
我們會發現裝飾器類同樣實現了IAccountService接口。最后我們在Main方法里面調用:
using StaticDemo.Model; using StaticDemo.Services; using System; namespace StaticDemo { class Program { static void Main(string[] args) { // 實例化對象 IAccountService accountService = new AccountService(); // 實例化裝飾器類,并用上面的實例給構造方法傳值 var account = new AccountDecorator(accountService); var user = new User { Name = "Rick", Password = "12345678" }; // 調用裝飾器類的注冊方法,相當于調用實例化對象的注冊方法 account.Reg(user); Console.ReadKey(); } } }
運行結果:
下面我們在來看看如何使用代理模式實現。
代理模式:即一個類代表另一個類的功能。我們會創建一個代理類,這個代理類和裝飾器類基本一樣。看一下代碼:
using StaticDemo.Model; using StaticDemo.Services; using System; namespace StaticDemo { /// <summary> /// 代理類 /// </summary> public class ProxyAccount : IAccountService { private readonly IAccountService _accountService; /// <summary> /// 構造函數沒有參數 /// 直接在里面創建了AccountService類 /// </summary> public ProxyAccount() { _accountService = new AccountService(); } public void Reg(User user) { before(); _accountService.Reg(user); after(); } private void before() { Console.WriteLine("代理:注冊之前的邏輯"); } private void after() { Console.WriteLine("代理:注冊之后的邏輯"); } } }
Main方法里面調用:
using StaticDemo.Model; using StaticDemo.Services; using System; namespace StaticDemo { class Program { static void Main(string[] args) { #region 裝飾器模式 //// 實例化對象 //IAccountService accountService = new AccountService(); //// 實例化裝飾器類,并用上面的實例給構造方法傳值 //var account = new AccountDecorator(accountService); //var user = new User { Name = "Rick", Password = "12345678" }; //// 調用裝飾器類的注冊方法,相當于調用實例化對象的注冊方法 //account.Reg(user); #endregion #region 代理模式 var account = new ProxyAccount(); var user = new User { Name = "Tom", Password = "12345678" }; account.Reg(user); #endregion Console.ReadKey(); } } }
運行結果:
可能有的人會發現,裝飾器類和代理類很相像,功能也一模一樣,僅僅是構造函數不同。那么裝飾器模式和代理模式有區別嗎?有些東西,形式上看起來區別很小,但實際上他們區別很大。它們在形式上確實一樣,不管是裝飾器類還是代理類,它們都要實現相同的接口,但是它們在運用的時候還是有區別的。
裝飾器模式關注于在一個對象上動態添加方法,而代理模式關注于控制對象的訪問。簡單來說,使用代理模式,我們的代理類可以隱藏一個類的具體信息。var account = new ProxyAccount();僅看這段代碼不看源碼,不知道里面代理的是誰。
當使用代理模式的時候,我們常常是在代理類中去創建一個對象的實例:_accountService = new AccountService()。而當我們使用裝飾器模式的時候,我們通常是將原始對象作為一個參數傳遞給裝飾器的構造函數。簡單來說,在使用裝飾器模式的時候,我們可以明確地知道裝飾的是誰,而且更重要的是,代理類里面是寫死的,在編譯的時候就確定了關系。而裝飾器是在運行時來確定的。
動態代理實現也有兩種方式;
通過代碼織入的方式。例如PostSharp第三方插件。我們知道.NET程序最終會編譯成IL中間語言,在編譯程序的時候,PostSharp會動態的去修改IL,在IL里面添加代碼,這就是代碼織入的方式。
通過反射的方式實現。通過反射實現的方法非常多,也有很多實現了AOP的框架,例如Unity、MVC過濾器、Autofac等。
我們先來看看如何使用PostSharp實現動態代理。PostSharp是一款收費的第三方插件。
首先新創建一個控制臺應用程序,然后創建一個訂單業務類:
using System; namespace PostSharpDemo { /// <summary> /// 訂單業務類 /// </summary> public class OrderBusiness { public void DoWork() { Console.WriteLine("執行訂單業務"); } } }
接著在Main方法里面調用:
using System; namespace PostSharpDemo { class Program { static void Main(string[] args) { OrderBusiness order = new OrderBusiness(); // 調用方法 order.DoWork(); Console.ReadKey(); } } }
運行結果:
這時又提出了一個新的需求,要去添加一個日志功能,記錄業務的執行情況,按照以前的辦法,需要定義一個日志幫助類:
using System; using System.IO; namespace PostSharpDemo { public class LgoHelper { public static void RecoreLog(string message) { string strPath = AppDomain.CurrentDomain.BaseDirectory+"\\log.txt"; using(StreamWriter sw=new StreamWriter(strPath,true)) { sw.WriteLine(message); sw.Close(); } } } }
如果不使用AOP,我們就需要在記錄日志的地方實例化Loghelper對象,然后記錄日志:
using System; namespace PostSharpDemo { /// <summary> /// 訂單業務類 /// </summary> public class OrderBusiness { public void DoWork() { // 記錄日志 LgoHelper.RecoreLog("執行業務前"); Console.WriteLine("執行訂單業務"); LgoHelper.RecoreLog("執行業務后"); } } }
我們再次運行程序,查看結果:
我們看看日志內容:
這樣修改可以實現記錄日志的功能。但是上面的方法會修改原先已有的代碼,這就違反了開閉原則。而且添加日志也不是業務需求的變動,不應該去修改業務代碼。下面使用AOP來實現。首先安裝PostSharp,直接在NuGet里面搜索,然后安裝即可:
然后定義一個LogAttribute類,繼承自OnMethodBoundaryAspect。這個Aspect提供了進入、退出函數等連接點方法。另外,Aspect上必須設置“[Serializable] ”,這與PostSharp內部對Aspect的生命周期管理有關:
using PostSharp.Aspects; using System; namespace PostSharpDemo { [Serializable] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class LogAttribute: OnMethodBoundaryAspect { public string ActionName { get; set; } public override void OnEntry(MethodExecutionArgs eventArgs) { LgoHelper.RecoreLog(ActionName + "開始執行業務前"); } public override void OnExit(MethodExecutionArgs eventArgs) { LgoHelper.RecoreLog(ActionName + "業務執行完成后"); } } }
然后Log特性應用到DoWork函數上面:
using System; namespace PostSharpDemo { /// <summary> /// 訂單業務類 /// </summary> public class OrderBusiness { [Log(ActionName ="DoWork")] public void DoWork() { // 記錄日志 // LgoHelper.RecoreLog("執行業務前"); Console.WriteLine("執行訂單業務"); // LgoHelper.RecoreLog("執行業務后"); } } }
這樣修改以后,只需要在方法上面添加一個特性,以前記錄日志的代碼就可以注釋掉了,這樣就不會再修改業務邏輯代碼了,運行程序:
在看看日志:
這樣就實現了AOP功能。
我們在看看使用Remoting來實現動態代理。
首先還是創建一個User實體類:
namespace DynamicProxy.Model { public class User { public string Name { get; set; } public string Password { get; set; } } }
然后創建一個接口,里面有一個注冊方法:
using DynamicProxy.Model; namespace DynamicProxy.Services { public interface IAccountService { void Reg(User user); } }
然后創建接口的實現類:
using DynamicProxy.Model; using System; namespace DynamicProxy.Services { public class AccountService : MarshalByRefObject, IAccountService { public void Reg(User user) { Console.WriteLine($"{user.Name}注冊成功"); } } }
然后創建一個泛型的動態代理類:
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; namespace DynamicProxy { public class DynamicProxy<T> : RealProxy { private readonly T _target; // 執行之前 public Action BeforeAction { get; set; } // 執行之后 public Action AfterAction { get; set; } // 被代理泛型類 public DynamicProxy(T target) : base(typeof(T)) { _target = target; } // 代理類調用方法 public override IMessage Invoke(IMessage msg) { var reqMsg = msg as IMethodCallMessage; var target = _target as MarshalByRefObject; BeforeAction(); // 這里才真正去執行代理類里面的方法 // target表示被代理的對象,reqMsg表示要執行的方法 var result = RemotingServices.ExecuteMessage(target, reqMsg); AfterAction(); return result; } } }
我們看到,這個泛型動態代理類里面有兩個泛型委托:BeforeAction、AfterAction。通過構造函數把代理泛型類傳遞進去。最后調用Invoke方法執行代理類的方法。
最后我們還要創建一個代理工廠類,用來創建代理對象,通過調用動態代理來創建動態代理對象:
using System; namespace DynamicProxy { /// <summary> /// 動態代理工廠類 /// </summary> public static class ProxyFactory { public static T Create<T>(Action before, Action after) { // 實例化被代理泛型對象 T instance = Activator.CreateInstance<T>(); // 實例化動態代理,創建動態代理對象 var proxy = new DynamicProxy<T>(instance) { BeforeAction = before, AfterAction = after }; // 返回透明代理對象 return (T)proxy.GetTransparentProxy(); } } }
我們最后在Main方法里面調用:
using DynamicProxy.Model; using DynamicProxy.Services; using System; namespace DynamicProxy { class Program { static void Main(string[] args) { // 調用動態代理工廠類創建動態代理對象,傳遞AccountService,并且傳遞兩個委托 var acount = ProxyFactory.Create<AccountService>(before:() => { Console.WriteLine("注冊之前"); }, after:() => { Console.WriteLine("注冊之后"); }); User user = new User() { Name="張三", Password="123456" }; // 調用注冊方法 acount.Reg(user); Console.ReadKey(); } } }
程序運行結果:
這樣就利用Remoting實現了動態代理。
感謝各位的閱讀!關于“C#中AOP編程思想是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。