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