溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#中AOP編程思想是什么

發(fā)布時間:2022-03-18 13:34:31 來源:億速云 閱讀:251 作者:小新 欄目:開發(fā)技術

這篇文章給大家分享的是有關C#中AOP編程思想是什么的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

一、什么是AOP

AOP:Aspect Oriented Programming的縮寫,意為面向切面編程,通過預編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術。AOP是OOP思想的延續(xù)。利用AOP可以對業(yè)務邏輯的各個部分進行隔離,從而使得業(yè)務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發(fā)的效率。

為什么要學習AOP呢?

AOP的應用場景非常廣泛,在一些高級工程師或者架構師的面試過程中,頻率出現(xiàn)的比較多。

二、編程思想的發(fā)展路線

1、POP

POP:Procedure Oriented Programming的縮寫,即面向過程編程,是一種以過程為中心的編程思想。

面向過程是分析出解決問題的步驟,然后用函數或者方法,把這些步驟一步一步的實現(xiàn),使用的時候在一個一個的一次調用函數或者方法,這就是面向過程編程。最開始的時候都是面向過程編程。面向過程是最為實際的一種思考方式。就算是面向對象編程,里面也是包含有面向過程的編程思想,因為面向過程是一種基礎的編程思考方式,它從實際出發(fā)來考慮如何實現(xiàn)需求。

POP的不足:面向過程編程,只能處理一些簡單的問題,無法處理一些復雜的問題。如果問題很復雜,全部以流程來思考的話,會發(fā)現(xiàn)流程很混亂,甚至流程都不能進行下去。

2、OOP

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)這個需求。

3、AOP

AOP:Aspect Oriented Programming的縮寫,即面向切面編程。是對OOP的一種補充,在不修改原始類的情況下,給程序動態(tài)添加統(tǒng)一功能的一種技術。

OOP關注的是將需求功能劃分為不同的并且相對獨立、封裝良好的類,依靠繼承和多態(tài)來定義彼此的關系。AOP能夠將通用需求功能從不相關的類中分離出來,很多類共享一個行為,一旦發(fā)生變化,不需要去修改很多類,只需要去修改這一個類即可。

AOP中的切面是指什么呢?切面指的是橫切關注點??聪旅嬉粡垐D:

C#中AOP編程思想是什么

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ā)過程中,這三種編程思想是不可或缺的。

三、實現(xiàn)AOP

我們在上面講解了有關AOP的一些理論知識,那么如何在代碼里面實現(xiàn)呢?

實現(xiàn)AOP有兩種方式:

  • 靜態(tài)代理實現(xiàn)。所謂靜態(tài)代理,就是我們自己來寫代理對象。

  • 動態(tài)代理實現(xiàn)。所謂動態(tài)代理,就是在程序運行時,去生成一個代理對象。

1、靜態(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();
        }
    }
}

運行結果:

C#中AOP編程思想是什么

下面我們在來看看如何使用代理模式實現(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();
        }
    }
}

運行結果:

C#中AOP編程思想是什么

可能有的人會發(fā)現(xiàn),裝飾器類和代理類很相像,功能也一模一樣,僅僅是構造函數不同。那么裝飾器模式和代理模式有區(qū)別嗎?有些東西,形式上看起來區(qū)別很小,但實際上他們區(qū)別很大。它們在形式上確實一樣,不管是裝飾器類還是代理類,它們都要實現(xiàn)相同的接口,但是它們在運用的時候還是有區(qū)別的。

裝飾器模式關注于在一個對象上動態(tài)添加方法,而代理模式關注于控制對象的訪問。簡單來說,使用代理模式,我們的代理類可以隱藏一個類的具體信息。var account = new ProxyAccount();僅看這段代碼不看源碼,不知道里面代理的是誰。

當使用代理模式的時候,我們常常是在代理類中去創(chuàng)建一個對象的實例:_accountService = new AccountService()。而當我們使用裝飾器模式的時候,我們通常是將原始對象作為一個參數傳遞給裝飾器的構造函數。簡單來說,在使用裝飾器模式的時候,我們可以明確地知道裝飾的是誰,而且更重要的是,代理類里面是寫死的,在編譯的時候就確定了關系。而裝飾器是在運行時來確定的。

2、動態(tài)代理

動態(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();
        }
    }
}

運行結果:

C#中AOP編程思想是什么

這時又提出了一個新的需求,要去添加一個日志功能,記錄業(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è)務后");
        }
    }
}

我們再次運行程序,查看結果:

C#中AOP編程思想是什么

我們看看日志內容:

C#中AOP編程思想是什么

這樣修改可以實現(xiàn)記錄日志的功能。但是上面的方法會修改原先已有的代碼,這就違反了開閉原則。而且添加日志也不是業(yè)務需求的變動,不應該去修改業(yè)務代碼。下面使用AOP來實現(xiàn)。首先安裝PostSharp,直接在NuGet里面搜索,然后安裝即可:

C#中AOP編程思想是什么

然后定義一個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è)務邏輯代碼了,運行程序:

C#中AOP編程思想是什么

在看看日志:

C#中AOP編程思想是什么

這樣就實現(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();
        }
    }
}

程序運行結果:

C#中AOP編程思想是什么

這樣就利用Remoting實現(xiàn)了動態(tài)代理。

感謝各位的閱讀!關于“C#中AOP編程思想是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

aop
AI