Unity应用架构设计(12)——AOP思想的实践

时间:2021-09-14 13:54:51

想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧。领会了Boss的意图之后,漫长的排查问题开始了。你会写日志,或者是其他工具来追踪原因。那么如何以一种优雅的形式,并且不侵入业务代码的形式来跟踪呢?这正是本文的内容。

跟踪问题

通过观察,你发现方法Do执行缓慢,可能有性能问题,因为这是一个线上的版本,你无法进行Debug,所以你通过日志的形式来追踪执行步骤:

class Foo1
{
void Do()
{
//日志记录开始
//性能监控开始
DoSomething();
//日志记录结束
//性能监控结束
}
}

看起来不错,解决问题之后,测试又发现另一个方法Handle貌似也有问题,然后你一样画葫芦,虽然麻烦了一点,但总归是有解决方案:

class Foo2
{
void Handle()
{
//日志记录开始
//性能监控开始
DoSomething();
//日志记录结束
//性能监控结束
}
}

上述两段代码,虽然看起来很丑,但毕竟是能解决问题,但是代码的风格不怎么让人舒服:

  • 代码没有重用,比如日志记录,性能监控,它们实则是做了一样的事情
  • 日志记录,性能监控代码侵入了核心的业务逻辑,造成了混乱

知道了问题之后,第二种风格的代码出现了:

class Bar
{
void Do()
{
common.BeginLog();
common.BeginWatch();
common.BeginTransaction();
foo.Do();
common.Commit();
common.EndWatch();
common.EndLog();
}
}

看似是个不错的方案,但实际上还是没解决本质问题。虽然将日志,监控放到了Common中,但每个方法还是要写这一大堆和业务无关的代码,这压根什么也没解决,这个方法的层次结构如下图所示:

Unity应用架构设计(12)——AOP思想的实践

AOP面向切面编程的引入

什么是AOP?

  • 名称来源->『Aspect Oriented Programming』的缩写,中文翻译即『面向切面编程』
  • 应用场景->为日志记录,性能监控,安全控制,事务处理,异常处理等与具体业务逻辑无关,却又需要在全局范围执行的功能提供了一种良好的重用并且和业务逻辑解耦
  • 核心理念->AOP的思想是围绕着切面进行的,所谓的『切面』就是对目标对象的某种操作进行拦截,在系统其他部分调用目标对象的某种操作时拦截这些调用,并在进行真正的调用前、后执行一段中间逻辑
  • 实现方式->AOP的实现方式被分为『静态织入』和『动态织入』。采用『静态织入』方式通过扩展编译器对代码的中间语言IL插入代码,从而对目标对象进行拦截。『动态织入』则在运行时创建动态代理对象来拦截

如果你是第一次接触『面向切面编程』,可能这些概念太过复杂和笼统,我建议先翻阅相关书籍、博客,最好对AOP有一定的了解。

什么是『切面』?

『面向切面编程』总共6个字,想必最难理解的还是『切面』两字吧。

  • 在现实生活中,一个大西瓜,一刀切下去,一分为二,这破坏了它的整体性,瓜瓤完全暴露在你面前,然后你可以随心所欲的吃它,但不切开,你肯定吃不了它。
  • 在计算机世界,一个类(对象)包裹了若干方法,它像西瓜那样也是一个整体,你将方法作为一个切入点,一刀切下去,方法完全暴露在你面前,你可以按照你所需的要求进行拦截,但不切开,你肯定拦截不了。当然,计算机世界里肯定不会让你拿一把刀来切一个对象,这是一个更加抽象的概念,下面会阐述

所以『切面』是一种横向的拦截,而非纵向继承的机制。

使用纵向继承的方式来拦截方法:

class Order
{
public virtual void Add()
{
Console.WriteLine("新增订单");
}
} class OrderExt : Order
{
public override void Add()
{
//开启事务
BeginTransaction();
base.Add();
//提交事务
Commit();
}
void BeginTransaction() { }
void Commit() { }
}

缺点上面已经提到过了,不再重复。

使用AOP横向拦截方法,通过动态代理类来实现:

class Order
{
public virtual void Add()
{
Console.WriteLine("新增订单");
}
} class TransactionUtility
{
public void BeginTransaction() { }
public void Commit() { }
} class OrderProxy
{
public Order o;
public TransactionUtility u;
public void Add()
{
u.BeginTransaction();
o.Add();
u.Commit();
}
}

当然这个OrderProxy一般是通过框架(比如Spring)在运行时动态创建的,所以叫动态代理对象。客户端不知道真正调用的对象其实是OrderProxy。整个过程如下所示:

Unity应用架构设计(12)——AOP思想的实践

1.) Target:目标类,需要被代理的类

2.) Join Point:连接点,指那些可能被拦截到的方法。

3.) Point Cut:切入点,已经被增强的连接点

4.) 切面类:提供了事务管理,日志记录,性能监控等公共操作的类

5.) Advice:通知点,用来增强代码

6.) Weaving:织入,将Advice应用到目标对象Target,是创建新的代理对象Proxy的过程。

7.) Proxy:代理类

8.) Aspect:切面,是切入点PointCut和通知Advice的结合,2点确定一条线,多条线组合成面

在Unity中使用AOP思想

很遗憾,在Unity中没有好的AOP框架,虽然.NET有很多AOP框架,但考虑到Unity的跨平台,很多技术并不兼容。所以我以另一种形式间接的实现了AOP。

理解了AOP之后,实际上我只关注两点:

  • 既然没有框架来动态创建代理对象,那我只能自己创建
  • 代理对象是用来拦截方法的,你需要告诉代理对象怎么来拦截方法,所以通过委托的形式来指定策略

定义一个Proxy类,指定需要被代理的类的方法,以及拦截策略:

public class Proxy
{
public static Proxy Instance = new Proxy(); private IInvocationHandler _invocationHandler;
private object _target;
private string _method;
private object[] _args; private Proxy()
{
} public Proxy SetInvocationHandler(IInvocationHandler invocationHandler)
{
_invocationHandler = invocationHandler;
return this;
} public Proxy SetTarget(object target)
{
_target = target;
return this;
} public Proxy SetMethod(string method)
{
_method = method;
return this;
} public Proxy SetArgs(object[] args)
{
_args = args;
return this;
} public object Invoke()
{
var methodInfo = _target.GetType().GetMethod(_method);
return _invocationHandler.Invoke(_target, methodInfo, _args);
}
}

拦截策略是个公共接口,提供策略:

public interface IInvocationHandler
{
void PreProcess();
object Invoke(object proxy, MethodInfo method, object[] args);
void PostProcess();
}

实现接口,就可以自定义拦截方法,一个日志的策略如下所示:

public class LogInvocationHandler:IInvocationHandler
{
public void PreProcess()
{
LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Pre Process");
} public object Invoke(object target, MethodInfo method, object[] args)
{
PreProcess();
var result= method.Invoke(target, args);
PostProcess();
return result;
} public void PostProcess()
{
LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Post Process");
}
}

假设你需要对repository对象的Test方法进行拦截,即执行前后进行日志的打印,你可以这样来使用:

 Proxy.Instance.SetTarget(repository)
.SetMethod("Test")
.SetArgs(new object[] {})
.SetInvocationHandler(new LogInvocationHandler())
.Invoke();

小结

AOP思想是非常重要的重构手段,以不侵入的形式解耦业务逻辑和拦截方法。本质上是以横向扩展的形式替换了传统的纵向继承方式来实现。遗憾的是,在Unity中并没有好的AOP框架,我按照AOP的思想,简化了实现模式,以曲线的形式实现对方法的拦截。

源代码托管在Github上,点击此了解

Unity应用架构设计(12)——AOP思想的实践的更多相关文章

  1. Unity应用架构设计&lpar;11&rpar;——一个网络层的构建

    对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...

  2. Unity应用架构设计&lpar;1&rpar;—— MVVM 模式的设计和实施&lpar;Part 2&rpar;

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

  3. Unity应用架构设计&lpar;4&rpar;——设计可复用的SubView和SubViewModel&lpar;Part 1&rpar;

    『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...

  4. Unity应用架构设计&lpar;1&rpar;—— MVVM 模式的设计和实施&lpar;Part 1&rpar;

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  5. Unity应用架构设计&lpar;13&rpar;——日志组件的实施

    对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...

  6. Unity应用架构设计&lpar;9&rpar;——构建统一的 Repository

    谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...

  7. Unity应用架构设计&lpar;7&rpar;——IoC工厂理念先行

    一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...

  8. Unity应用架构设计&lpar;6&rpar;——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  9. Unity应用架构设计&lpar;2&rpar;——使用中介者模式解耦ViewModel之间通信

    当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...

随机推荐

  1. 页面元素坐标和偏移(clientX&sol;pageX&sol;screenX&sol;layerX&sol;offsetWidth&sol;scrollWidth&sol;clientWidth等)相关整理

    鼠标事件都是在特定位置发生的,我们可以通过event事件对象的各种属性来获得事件发生的坐标位置,有相对于视口的,有相对于整个文档的,同样页面元素的位置也有相对视口的,也有滚动后的,这些都比较容易混淆, ...

  2. python-redis 入门

    redis官网http://redis.io Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. Redis是一 ...

  3. vs开发工具使用问题

    1.快捷键失效:(如:VS中CTRL+E,CTRL+D自动缩进功能不能使用) 通过设置“工具”-“选项”-“环境”-“键盘”-“应用以下其他键盘映射方案”中,选择"Visual C# 200 ...

  4. android之tween动画详解

    android中一共提供了两种动画,其一便是tween动画,tween动画通过对view的内容进行一系列的图像变换(包括平移,缩放,旋转,改变透明度)来实现动画效果,动画效果的定义可以使用xml,也可 ...

  5. lc面试准备&colon;Number of 1 Bits

    1 题目 Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also ...

  6. ABP展现层——动态生成WebApi

    ABP展现层——动态生成WebApi 点这里进入ABP系列文章总目录 ABP(现代ASP.NET样板开发框架)系列之20.ABP展现层——动态生成WebApi ABP是“ASP.NET Boilerp ...

  7. 6&period;1、Android Studio的Android Monitor概览

    Android Monitor帮助你监测你的应用的性能,以帮助你合理的进行优化,调试,提升.如下功能: 1. Log消息,系统定义的或者开发者定义的. 2. 内存,CPU和GPU使用情况. 3. 网络 ...

  8. 6&period;6安装编译安装zabbix3&period;2

    添加yum源 get -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo #安装阿里云y ...

  9. Supporting Right-to-Left Languages

    For the most part iOS supports Right-to-Left (RTL) languages such as Arabic with minimal developer e ...

  10. selenium 基本常用操作

    from selenium import webdriverfrom selenium.webdriver.common.action_chains import ActionChains #鼠标操作 ...