利用AOP实现空模式的无缝使用 创世纪代码应用之一:一行代码让接口框架RUN起来

这是我开播第一篇,朋友们多多支持、捧场,谢谢。

引子

地是空虚混沌.渊面黑暗. 神的灵运行在水面上。  
神说、要有光、就有了光。  
神看光是好的、就把光暗分开了。  
神称光为昼、称暗为夜.有晚上、有早晨、这是头一日。

——引至《圣经.神创造天地》

关键词:null,AOP,Spring.Net框架,空模式,面向接口编程,单元测试,方法拦截器

摘要:在我们编程的时候很难离开null,它给我们带来了很多麻烦。本文从新的视角利用AOP无缝使用空模式部分解决了这个问题,最重要的是可以使得我们的程序尽早运行起来……

问题由来

null在所有编程语言里面都是一个非常重要的概念,在不同的场景下有不同的含义。我们编程的时候很难离开它,但是它也给我们带来了很多麻烦。就像数学里面的0一样,只要是除法运算,必须保证分母不能为0,否则推导出的结论可能就是错误的。

我们可能不止一次编写如下语句:

if(employee!=null)……

或者

if(employee==null)……

没有办法,只要employee是null,如果不判断而直接使用了employee的任何成员都会产生异常,因为null是没有任何成员的。这样到处逻辑判断的代码不仅影响理解,而且如果忘记判断还可能会引起异常。有没有什么方法解决这个问题?

空模式概念

可以使用空模式对象代替null,避免大量的if(xx==null)判断语句,增强程序的健壮性。

如果我们在实现employee类型的时候按照同样的接口定义一个NullEmployee类型,使他的所有方法都返回缺省值,所有的集合属性都尽可能是不包含元素的空集合。那么在返回employee类型的方法没有查到符合条件的雇员时,返回的就不是null,而是NullEmploy的一个实例。那么这个实例虽然像普通雇员一样执行了所有的代码,但是对结果没有什么本质的影响。

更多此模式的论述参看《敏捷软件开发:原则、模式与实践(C#版)》的NULL OBJECT模式章节内容 ,不过我们是利用AOP无缝的使用从而达到我们的目的,并且大大提升了他的地位,在这一篇是绝对的第一主角。

对于空模式,我认为应该在源代码层面就杜绝其实例状态改变的可能,做法如下:方法如果有返回值则返回空集合或者缺省值,否则保持为空方法体;属性的set,get原则和上面相同。这样就算修改了空模式对象的属性,返回的还是缺省值。

在下面的实现中,我们仅仅利用了Aop拦截方法执行权的功能。而具有这个功能的框架非常多,其中Spring是近年来比较流行的轻量级框架,使用方法也非常简单,最关键的是它是开源的。

使用Spring框架实现的目的:

1为了扩展无缝使用空模式的应用场景;

2提供一个自动定义空模式类型的通用解决方案。

优点:

  • 使用缺省值暂时作为填充代码,使得设计成果早日运行起来 ,以后逐步使用实际代码填充;
  • 可以跨平台使用,这个原理在Java上面可以非常容易的实现;
  • 不需要定义空模式类型,使用更简单;
  • 代码少,逻辑清晰;

缺点:

  • 使用场景有限制,如果不是无参数构造器的集合或者接口,返回的还是null;
  • 需要依赖Spring框架;
  • 如果返回的空模式对象传入了第三方代码,而此代码在if(xx==null)情况下使对象本身或相关对象的状态有了改变,可能会有不合常规的结果。原因是一般为null时什么都不做,而这个第三方代码显然违背了常理;这样造成的bug很难发现。

原理

AOP(面向切面编程)的原理非常简单:可以在我们调用方法的前面,后面或者异常的时候添加一些验证、日志等等功能。用代码演示如下:

public void Method(int arg){

//里面为实现实际功能的代码

}

//下面的方法一般与上面的不在同一个类里面。

public void Login(){

//里面为实现登录功能的代码

}

public void Logoff(){

//里面为退出功能的代码

}

使用AOP包装包含Method方法的对象后,我们执行Method方法的时候实际执行的是类似如下代码:

public object Invoke(IMethodInvocation invocation) {
         //这个拦截方式的功能最强大,其他拦截方式与本文无关,不作解释。
         xx.Login();//调用方法前插入的功能

var obj=invocation .Proceed();//调用Method方法

xx.Logoff();//调用方法后插入的功能

return obj;

    }
invocation里面包含了调用Method方法的必须元素:返回类型,名称,参数列表等等信息。
而前面的代码之间是平等的顺序调用关系,三个方法是互不干扰的。我们这里则与平常的使用方式大不相同。我们取出包含的返回类型信息,然后判断为接口或集合则返回空模式对象。根本不调用var obj=invocation .Proceed();从而实现Spring创建的代理返回空模式对象的功能。

使用场景

假设我们需要开发一套公司管理软件,功能是可以查询公司名称,员工情况。很明显,最少需要两个类型:公司和员工。这里处于演示目的,采用的语言为c#,不考虑设计是否符合真实逻辑。我们定义接口如下:

public interface ICompany {

string Name { get; }

IList<IEmployee> Employees { get; }

IEmployee GetEmployee(string name);

string GetEmployeeStatus(IEmployee employee);

}

public interface IEmployee {

string Name { get; }

double Salay { get; }

int Id { get; }

}

这里仅仅定义了两个接口,还没有写一行真正意义可以运行的代码,就认为这是一个构想中的空公司吧。如果细心,你会发现ICompany定义里面有多处IEmployee接口的使用,这就是面向接口编程实际应用。我们测试一下是否符合要求:

[TestClass]

public class CompanyTest{

[TestMethod]

public void TestNullCompany(){

var nullCompany = AdapterFactory.CreateNullInstance<ICompany>();

TestNullCompany(nullCompany);

TestNullEmployee(nullCompany);

}

private void TestNullEmployee(ICompany company){

var nullEmployee = company.GetEmployee("Tom"); //无中生有的职工,空职工

Assert.IsNotNull(nullEmployee); //空职工也是职工.

Assert.IsNull(nullEmployee.Name); //空职工没有名字,合理.

Assert.IsTrue(nullEmployee.Id == 0); //连身份号也是0,合理

Assert.AreEqual(nullEmployee.Salay, 0); //果然,没有薪水.

}

private void TestNullCompany(ICompany nullCompany){

Assert.IsNotNull(nullCompany); //虽然是空公司,还是公司。

Assert.IsNull(nullCompany.Name); //空壳公司没有名分,合理.

Assert.AreEqual(nullCompany.Employees.Count, 0); //没有职工,合理.

}

}

在上面的代码中,真正有意义的只有这一句:

var nullCompany = AdapterFactory.CreateNullInstance<ICompany>();

调用一个工厂方法,创建一个实现了ICompany接口的nullCompany对象,其余测试代码是对此对象状态的判断。看到没有,我们仅仅定义了两个接口,连实现接口的类型都没有实现,可是已经可以对其逻辑进行判断和验证了。这就像设计一座大桥,需要首先根据图纸尺寸建造一个缩小版模型,然后用这个模型进行地震、风力、浪涌、撞击、共振等等破坏性实验,根据结果调整设计。而不能等大桥建造好了使用实物进行上述的实验。这个nullCompany对象虽然什么都不能做,但是它已经运行起来了,这才是非常重要的。

实现功能的后台代码

下面就让我们看看AdapterFactory类型这个幕后大哥的真容吧。

public static class AdapterFactory {
  /// <summary>
  /// 创建实现传入接口的实例,实际执行的代码为传入的对象。
  /// </summary>
  /// <typeparam name="T">创建实例需要实现的接口</typeparam>
  /// <param name="inst">与接口签名对应的对象</param>
  /// <param name="aroundInterceptor">拦截方式</param>
  /// <returns>实现了接口的实例</returns>
  private static T CreateAdapter<T>(object inst = null, IMethodInterceptor aroundInterceptor = null) {
    var objects = new[] { inst };
    if (inst == null)
      objects = new object[0];
    return (T)CreateAdapter(objects, aroundInterceptor, new[] { typeof(T) });
  }
  /// <summary>
  /// 创建实现传入接口的实例,实际执行的代码为传入的对象。
  /// </summary>
  /// <param name="inst">与接口签名对应的对象</param>
  /// <param name="aroundInterceptor">拦截方式</param>
  /// <param name="interfacTypes">创建实例需要实现的接口</param>
  /// <returns>实现了接口的实例</returns>
  private static object CreateAdapter(IEnumerable<object> inst, IMethodInterceptor aroundInterceptor, Type[] interfacTypes) {
    var proxy = new ProxyFactory(interfacTypes);
    if (inst != null)
      proxy.Target = inst;
    if (aroundInterceptor != null)
      proxy.AddAdvice(aroundInterceptor);
    var instance = proxy.GetProxy();
    return instance;
  }
  /// <summary>
  /// 利用接口创建一个空实例。
  /// </summary>
  /// <typeparam name="T">空实例实现的接口</typeparam>
  /// <returns>实现了T接口的空实例</returns>
  public static T CreateNullInstance<T>(){
    var type = typeof(T);
    if (!type.IsInterface)
      return default(T);
    return CreateAdapter<T>(null, new ReturnNullPattern());
  }

  /// <summary>
  /// 利用接口创建一个空实例。
  /// </summary>
  /// <param name="type">name="T">空实例实现的接口</param>
  /// <returns>实现了type接口的空实例</returns>
  public static object CreateNullInstance(Type type) {
    return CreateAdapter(null , new ReturnNullPattern(),new []{type});
  }
}
真正的核心是CreateAdapter方法,原理非常简单,使用Spring AOP的代理工厂创建一个包含实现接口的代理对象。其他方法都是对这个方法的包装,需要说明的只有CreateNullInstance<T>()里面的最后一句:
return CreateAdapter<T>(null,newReturnNullPattern());
它需要一个ReturnNullPattern类型的实例,此类型的代码如下:

初始版本拦截器

public class ReturnNullPattern : IMethodInterceptor {
  public virtual object Invoke(IMethodInvocation invocation) {
    var methodInfo = invocation.Method;
    var returnType = methodInfo.ReturnType;
    return NullInstance(null, returnType);
  }
  protected object NullInstance(object v, Type type){
    if (v != null) return v;
    if (type.IsInterface) {
      return AdapterFactory.CreateNullInstance(type);
    }
    if (type.GetInterfaces().Contains(typeof(IEnumerable))
        && type.IsClass) {
      return CreateNullCollection(type);
    }
    if (!type.IsClass){
      try{
        v = Activator.CreateInstance(type);
      }
      catch{
        
      }
    }
    return v;
  }

  private object CreateNullCollection(Type type){
    try {
      return Activator.CreateInstance(type);
    }
    catch{
        return null;
    }
  }
}
此类型实现了IMethodInterceptor接口,也就意味着代理对象nullCompany调用任意成员的时候都会执行此类型的Invoke(IMethodInvocation invocation)方法。注意这里拦截后与普通AOP编程的不同,它没有调用真正执行代码的方法,因为现在这个方法还不存在呢。invocation参数里面包含了调用信息,我们这里只需要使用它的返回类型。根据类型返回其缺省值,如果是接口则返回一个空模式的对象。如下:
if (type.IsInterface) {
      return AdapterFactory.CreateNullInstance(type);
    }
因此,把参数、返回值的类型用接口表示是非常关键的,这也符合近年来面向接口编程的趋向。其他代码非常简单,就不需要特别说明了。

小结:

一个方法的典型定义如下:ReturnType MethodName(Arg1Type arg1,……)本篇通过利用返回类型也就是ReturnType构建空模式对象的返回值,代替null,解决了对象为空时调用成员发生异常的问题。附带加强了读者面向接口编程的意识。当然,有时候返回null也是有必要的,我们可以在接口的对应成员上面添加一个特性,在拦截器里面判断是否有此特性,从而确定返回null还是空模式对象。限于篇幅,这个就留给读者自行完成了。下一篇则接着介绍如果执行方法的传入参数也就是arg1,……为null时,使用空模式对象代替后会发生什么有趣的事情。

时间: 2024-11-03 12:23:25

利用AOP实现空模式的无缝使用 创世纪代码应用之一:一行代码让接口框架RUN起来的相关文章

AOP之代理模式(一)

AOP,为Aspect OrientedProgramming的缩写,意为:面向切面,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 说了一堆很官方的话,大家可能不是特别明白,不过这些算是我们实践的理论寄出去,还是很有必要知道的,但是现在不必要很懂,接下来从最简单的代码开始,一步步的慢慢深入了解,到底什么是AOP,什么是代理模式.学习,就是这样一个理论与

Spring+SpringMVC+Mybatis 利用AOP自定义注解实现可配置日志快照记录

目的: 需要对一些事物的操作进行日志记录,如果在service内进行记录,大量的代码重复,并且维护比较麻烦.所以采用AOP的方式对service进行拦截.使用自定义注解的目的则是判断是否需要记录日志和传递额外的信息. 方式 本次解决方案十分感谢博主-跳刀的兔子的博文 本文绝大部分参考与本文,略有不同,所以做一些整理,博主的文章更详细一些. 1.首先新建自定义注解 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @

WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务

原文:WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务 在<基于IIS的WCF服务寄宿(Hosting)实现揭秘>中,我们谈到在采用基于IIS(或者说基于ASP.NET)的WCF服务寄宿中,具有两种截然不同的运行模式:ASP.NET并行(Side by Side)模式和ASP.NET兼容模式.对于前者,WCF通过HttpModule实现了服务的寄宿,而对于后者,WCF的服务寄宿通过一个HttpHandler实现.只有在ASP.NET兼容模式下,我们熟悉的

SpringAOP01 利用AOP实现权限验证、利用权限验证服务实现权限验证

1 编程范式 1.1 面向过程 1.2 面向对象 1.3 面向切面编程 1.4 函数式编程 1.5 事件驱动编程 2 什么是面向切面编程 2.1 是一种编程范式,而不是一种编程语言 2.2 解决一些特定的问题 2.3 作为面向对象编程的一种补充 3  AOP产生的初衷 3.1 解决代码重复性问题 Don't Repeat Yourself 3.2 解决关注点分离问题 Separation of Concerns 3.2.1 水平分离(技术上划分) 控制层 -> 服务层 -> 持久层 3.2.2

利用多线程实现Future模式

一.Futrue模式 客户端发送一个长时间的请求,服务端不需等待该数据处理完成便立即返回一个伪造的代理数据(相当于商品订单,不是商品本身),用户也无需等待,先去执行其他的若干操作后,再去调用服务器已经完成组装的真实数据. 该模型充分利用了等待的时间片段.简单来说就是,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果. 在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的

C#利用微软自带库进行中文繁体和简体之间的转换的代码

做工程之余,将做工程过程比较重要的代码备份一次,如下资料是关于C#利用微软自带库进行中文繁体和简体之间的转换的代码,应该是对码农有所帮助. protected void Button1_Click(object sender, EventArgs e) { if (string.IsNullOrEmpty(txt_value.Text)) { return; } else { string value = txt_value.Text.Trim(); string newValue = Stri

AOP基础—代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务. 按照代理的创建时期,代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行

如何利用aop的环绕消息处理log, 以及各种坑的记录

本文链接: https://www.cnblogs.com/zizaiwuyou/p/11667423.html 因为项目里有很多地方要打log, 所以决定改为AOP统一处理, 好不容易整好了, 特此记录一下: 一, 新建项目, 添加注解类和切面类 pom.xml文件如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/

利用反射实现工厂模式

需求:工厂类根据参数生成对应类的实例. 示例: RoomParts.cs namespace ReflectionFactory { /// <summary> /// 屋子产品的零件 /// </summary> public enum RoomParts { Roof, Window, Pillar } } ProductAttribute.cs using System; namespace ReflectionFactory { /// <summary> //