运维开发网

C编程的AOP编程思想

运维开发网 https://www.qedev.com 2022-06-22 21:23 出处:网络
这篇文章介绍了C#编程之AOP编程思想,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

这篇文章介绍了C#编程之AOP编程思想,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下


一、什么是AOP

AOP:英文缩写AOP:Aspect Oriented Programming,意为面向方面编程的一种技术,在运行时通过预编译和动态代理实现程序功能的统一维护。AOP是OOP思想的延续。AOP可以用来隔离业务逻辑的各个部分,从而降低业务逻辑各个部分之间的耦合度,提高程序的复用性和开发效率。

为什么要学习AOP?

AOP的应用场景非常广泛,在一些高级工程师或架构师的面试过程中频繁出现。


二、编程思想的发展路线


1、POP

POP:procedure oriented programming的缩写,即面向过程编程,是一种以过程为中心的编程思想。

面向过程是分析解决问题的步骤,然后用函数或方法一步步实现这些步骤。使用时,逐个调用函数或方法。这是面向过程的编程。一开始都是面向过程的编程。面向过程是最实用的思维方式。即使是面向对象编程,也包含着面向过程编程的思想,因为面向过程是一种基本的编程思维方式,它从实际出发考虑如何实现需求。

POP的缺点:面向过程编程只能处理一些简单的问题,不能处理一些复杂的问题。如果问题很复杂,我们都从过程的角度去思考,会发现过程是如此的混乱,甚至连过程都无法进行下去。


2、OOP

OOP:缩写OOP:Object Oriented Programming,即面向对象编程。

早期的计算机编程是面向过程的,因为早期编程相对简单。但是随着时间的发展,需要处理的问题越来越多,问题也会越来越复杂。这时候面向过程的编程就不能简单的用了,于是面向对象的编程就出现了。在电脑里,把一切想象成一件事。现实世界中的物体都有一些属性和行为,与计算机中的属性和方法相对应。

面向对象编程就是把组成一个问题的东西分解成各种对象。建立对象的目的不是为了完成一个步骤,而是描述一个事物在解决一个问题的整个步骤中的行为。

让我们以一个建筑为例来说明OOP的不足。

我们把系统比作一个建筑,其中类或对象是砖,砖形成一面墙,多面墙形成一个房间,多个房间形成一个建筑。
就好像一个模块的功能是由多个类实现的,模块形成某个服务,多个服务形成一个完整的系统。一个系统开发的完成并不意味着它真的完成了。以后肯定会有各种要求的变化。在需求发生变化后,我们必须修改代码。代码都在类里,相当于修改了类。如果是小范围改装,影响不大;如果是大规模改装,影响会更大。即使每一次修改都很小,但如果经常修改,影响会很大。会造成系统的不稳定。我们得出的结论是,类应该是固定的,不应该频繁修改,甚至不允许修改。这就是为什么有这么多的设计原则和模式。大多数设计模式都是为解决这类问题而设计的,即在不修改类的情况下扩展功能。

OOP的缺点:新的需求会导致程序代码的不断修改,容易导致程序的不稳定。

如果我们非常了解OOP,那么我们应该知道,从对象组织的角度来看,所有的分类方法都是基于继承关系的,我们称之为垂直。如果只用OOP思想,会带来两个问题:
1。常见问题。
2。扩展问题,在现有类需要扩展的时候比较困难。

OOP和POP的区别:

与面向过程相比,面向对象的方法是将事物最小化成对象,包括属性和方法。当程序规模比较小时,面向过程编程还是有一定优势的,因为这个时候程序的过程更容易梳理。以早上上班为例。流程是起床,穿衣,刷牙洗脸,去公司。每一步都是按顺序完成的,我们只需要一步一步的实现方法,最后依次调用实现的方法,这就是面向过程的开发。

如果用面向对象编程,需要抽象一个employee类,有起床、穿衣、刷牙洗脸、去公司四个方法。但是,最终要实现这个早上上班的要求,还是要依次调用四个方法。一开始我们按照面向过程的思路思考这个需求,然后按照面向对象的思路抽象出几种方法,最后实现这个需求,还是按照面向过程的顺序。

面向对象和面向过程的区别只是在思考问题的方式上。最后你会发现,当你实现这个需求的时候,即使你用面向对象的思想抽象出employee类,你还是要用面向过程的方法来实现这个需求。


3、AOP

AOP:简称AOP:Aspect Oriented Programming,即面向方面编程。它是对OOP的补充,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的理论知识,那么如何在代码中实现呢?

实现AOP有两种方法:

静态代理实现。所谓静态代理,就是我们自己来写代理对象。动态代理实现。所谓动态代理,就是在程序运行时,去生成一个代理对象。


1、静态代理

实现静态代理需要两种设计模式:装饰模式和代理模式。

装饰器模式:允许您在不改变现有对象结构的情况下向现有对象添加新函数。属于结构化设计模式,是对现有类的一种包装。首先,将创建一个装饰类来包装原始类并提供附加功能,同时保持类的完整性。看下面这个例子。

让我们首先创建一个用户类:

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{ /// lt;summarygt; /// 接口 /// lt;/summarygt; public interface IAccountService { /// lt;summarygt; /// 注册用户 /// lt;/summarygt; /// lt;param name="user"gt;lt;/paramgt; void Reg(User user); }}

然后创建一个类来实现上面的接口:

using StaticDemo.Model;using System;namespace StaticDemo.Services{ /// lt;summarygt; /// 实现IAccountService接口 /// lt;/summarygt; public class AccountService : IAccountService { public void Reg(User user) { // 业务代码 之前 或者之后执行一些其它的逻辑 Console.WriteLine($"{user.Name}注册成功"); } }}

我们正在创建一个装饰类:

using StaticDemo.Model;using StaticDemo.Services;using System;namespace StaticDemo{ /// lt;summarygt; /// 装饰器类 /// lt;/summarygt; 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("注册之后的逻辑"); } }}

我们会发现decorator类也实现了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{ /// lt;summarygt; /// 代理类 /// lt;/summarygt; public class ProxyAccount : IAccountService { private readonly IAccountService _accountService; /// lt;summarygt; /// 构造函数没有参数 /// 直接在里面创建了AccountService类 /// lt;/summarygt; 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(); } }}

运行结果:


有些人可能会发现,decorator类与proxy类非常相似,其功能完全相同,只是构造函数不同。那么装饰者模式和代理模式有区别吗?有些东西形式上看起来很不一样,其实很不一样。它们在形式上完全一样。无论是装饰类还是代理类,都要实现同一个接口,只是应用不同。

装饰模式侧重于向对象动态添加方法,而代理模式侧重于控制对象的访问。简单来说,使用代理模式,我们的代理类可以隐藏一个类的具体信息。var account = new proxy account();不看源代码只看这段代码。我不知道谁在里面表演。

在使用代理模式时,我们经常会在代理类中创建一个对象的实例:_ accountservice = new accountservice()。当我们使用装饰器模式时,我们通常将原始对象作为参数传递给装饰器的构造函数。简单来说,在使用decorator模式时,我们可以清楚地知道谁被修饰了,更重要的是代理类是写死的,关系是在编译时确定的。装饰器是在运行时确定的。


2、动态代理

实现动态代理也有两种方式;

通过代码织入的方式。例如PostSharp第三方插件。我们知道.NET程序最终会编译成IL中间语言,在编译程序的时候,PostSharp会动态的去修改IL,在IL里面添加代码,这就是代码织入的方式。通过反射的方式实现。通过反射实现的方法非常多,也有很多实现了AOP的框架,例如Unity、MVC过滤器、Autofac等。

我们先来看看如何使用PostSharp实现动态代理。PostSharp是收费的第三方插件。

首先创建一个新的控制台应用程序,然后创建一个订单业务类:

using System;namespace PostSharpDemo{ /// lt;summarygt; /// 订单业务类 /// lt;/summarygt; 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{ /// lt;summarygt; /// 订单业务类 /// lt;/summarygt; public class OrderBusiness { public void DoWork() { // 记录日志 LgoHelper.RecoreLog("执行业务前"); Console.WriteLine("执行订单业务"); LgoHelper.RecoreLog("执行业务后"); } }}

让我们再次运行程序,看看结果:


让我们来看看日志内容:


这种修改可以实现日志功能。但上述方法会修改现有代码,违背了开放封闭原则。而且添加日志并不是业务需求的改变,业务代码也不应该修改。下面用AOP来实现。先安装PostSharp,直接在NuGet里搜索,然后安装:


然后定义一个LogAttribute类,该类继承自OnMethodBoundaryAspect。这个方面提供了连接点方法,比如进入和退出函数。另外,ldquo必须设置在Aspect上;【可序列化】rdquo,这与PostSharp中方面的生命周期管理有关:

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 + "业务执行完成后"); } }}

然后将日志功能应用于DoWork函数:

using System;namespace PostSharpDemo{ /// lt;summarygt; /// 订单业务类 /// lt;/summarygt; public class OrderBusiness { [Log(ActionName ="DoWork")] public void DoWork() { // 记录日志 // LgoHelper.RecoreLog("执行业务前"); Console.WriteLine("执行订单业务"); // LgoHelper.RecoreLog("执行业务后"); } }}

这次修改后,方法只需要增加一个特性,之前记录日志的代码可以注释掉,这样业务逻辑代码就不会再修改了。运行程序:


查看日志:


这样就实现了AOP功能。

我们正在考虑使用远程处理来实现动态代理。

或者先创建一个用户实体类:

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 DynamicProxylt;Tgt; : 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{ /// lt;summarygt; /// 动态代理工厂类 /// lt;/summarygt; public static class ProxyFactory { public static T Createlt;Tgt;(Action before, Action after) { // 实例化被代理泛型对象 T instance = Activator.CreateInstancelt;Tgt;(); // 实例化动态代理,创建动态代理对象 var proxy = new DynamicProxylt;Tgt;(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.Createlt;AccountServicegt;(before:() =gt; { Console.WriteLine("注册之前"); }, after:() =gt; { Console.WriteLine("注册之后"); }); User user = new User() { Name="张三", Password="123456" }; // 调用注册方法 acount.Reg(user); Console.ReadKey(); } }}

程序运行结果:


这样,Remoting就被用来实现动态代理。

GitHub地址:https://github.com/jxl1024/AOP

这就是这篇关于C#编程的AOP编程思想的文章。希望对大家的学习有所帮助

0

精彩评论

暂无评论...
验证码 换一张
取 消