前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Aop学习笔记系列一

Aop学习笔记系列一

作者头像
郑小超.
发布2018-01-26 15:29:58
6350
发布2018-01-26 15:29:58
举报
文章被收录于专栏:GreenLeavesGreenLeaves

一、Aop解决了什么问题?

1、在说解决了什么问题之前,先介绍一些关键的知识点

a、功能需求:功能需求指项目中的增值需求,比如业务逻辑,UI,持久化(数据库)。

b、非功能需求:项目中次要的,但却不可缺少的元素,如日志记录,安全,性能和数据事务等等。

2、横切关注点

它是推动Aop发明的主要因素之一,横切关注点是应用与一个系统的多个部分的片段功能(相当于一个功能应用于系统的多个地方),横切关注点和非功能性需求有许多重叠:非功能性需求经常横切应用程序的多个部分。举例:如果有两个方法a和b,如果都要记录日志c,那么就必须在a和b中放置c,这里的c就是横切关注点。

ok,如果你清楚了上面的知识点,下面开始说Aop解决了什么问题。

在OOP项目中,有非常多的横切关注点分布在项目中,久而久之,这种交错的代码会变的越来越难开发和维护,这是OOP技术不能捕获和解决的问题所以Kiczales和他的团队提出了Aop的概念,并将其作为OOP的一种补充,即使用"切面aspects"封装横切关注点以及允许重复使用。最终实现了AspectJ,就是今天Java开发者仍然使用的一流AOP工具。AOP官方文档

3、切面的任务:通知(Advice)

如果你理解了2的横切关注点,那么通知就是执行横切关注点的代码,比如对于横切关注点-日志功能,那么通知可能是log4net或者其他日志库的调用代码。

4、切面的映射:切入点(PointCut)

切入点就相当于在哪里放置通知(advice)的代码,也就是在哪里放置执行横切关注点的代码,下面通过一行代码来解释:

代码语言:javascript
复制
dataService.AddRecord();//dataService是DataService的实例

当确认了一个连接点和一个通知,就可以定义切面了。切面通过叫做组合(weaving)的过程工作。

5、Aop开始组合(weaving)

5.1、 在没有Aop的时候,有两点你需要知道

a、缠绕(tangling),系统的核心业务逻辑往往是和横切关注点缠绕在一起的.代码如下:

代码语言:javascript
复制
    class Moudle
    {
        //日志成员
        public void Method()
        {
            //日志记录开始
            //核心代码
            //日志记录结束
        }
    }

上面这个过程就是缠绕,横切关注点日志功能,和核心代码缠绕在一起

b、分散,当横切关注点用于多个方法和多个类时,代码分散在整个应用中.代码如下:

代码语言:javascript
复制
    class Moudle
    {
        //日志成员
        public void Method()
        {
            //日志记录开始
            //核心代码
            //日志记录结束
        }
    }
    class Moudle1
    {
        //日志成员
        public void Method()
        {
            //日志记录开始
            //核心代码
            //日志记录结束
        }
    }

当横切关注点出现在了两个及以上的类或者方法中,这种形式就叫做分散,因为代码分散在整个应用中。

c、违反"单一职责"原则,一个类应该只有一个要修改的理由,不能因为类中参杂着的横切关注点的变化,而去修改他,这样的类设计是不合理的.

d、反模式:反模式是软件工程已确认的一种模式,例如你可以在“Gang of Four book”(全名是:设计模式:可复用面向对象软件的基础)中找到任何模式,跟那些好的模式不同,反模式会导致bug,产生昂贵的维护费用以及令人头疼的问题。

e、横切关注点的增多,如果你不及时采用DI或者装饰着模式或者Aop,那么你就会一直的处于复制黏贴的状态,从而违反了Don't Repeat yourself(DRY)原则!

5.2、解决方案

通过DI(依赖注入),代码如下:

代码语言:javascript
复制
    class Moudle
    {
        //_核心成员
        //_日志成员

        public Moudle(//日志接口 成员)    这里将日志接口通过DI注入进来
        { 
            //this._日志成员=成员;
        }
        public void Method()
        {
            //_日志成员.日志记录开始
            //核心代码
            //_日志成员.日志记录结束
        }
    }

通过代码发现,即使使用了依赖注入,代码仍然是缠绕的。

通过通过DI(依赖注入)+装饰者,代码如下:

代码语言:javascript
复制
    class MoudleDecorator
    {
        //_realmoudle
        //_日志成员

        public MoudleDecorator(//Moudle moudle,日志接口 成员)    这里将日志接口通过DI注入进来
        { 
            //this._realmoudle=moudle;
            //this._日志成员=成员;
        }
        public void Method()
        {
            //_日志成员.日志记录开始
            //moudle.Method();
            //_日志成员.日志记录结束
        }
    }

    class Moudle
    {
        public void Method()
        {
            //核心代码
        }
    }

ok,我们发现装饰着能很好的解决问题,但是如果这种装饰类一多,这个代码量好像又上去了,所以又开始重复造轮子了,所以当超过3个装饰者的时候,就可以考虑改用Aop的切面了。

so,终极方案Aop登场,先看图

Aop就是为了解决上面的问题,下面使用Aop对上面的代码进行重构

代码如下:

代码语言:javascript
复制
    class Moudle
    {
        //[LoggingAspect]
        public void Method()
        {
            //核心代码
        }
    }

    class LoggingAspect
    { 
        //_日志成员
        public void LoggingAspect(//日志接口 成员)  通过依赖注入
        {
            //this._日志成员=成员;
        }

        void OnEntry()
        {
            //日志记录开始
        }


        void OnSuccess()
        {
            //日志记录结束
        }
    }

通过Aop重构之后的代码更易于管理,更不容易出bug,如果你的Aop工具类库是稳定的话,代码的可读性也更强,更容易维护,降低维护的开销,如果使用Aop重构代码将横切关注点单独封装到一个切面类中,你就不用到处修改代码,只需要在一个类中修改就可以了。

下面是一个伪代码类,由于横切关注点而没有遵守单一职责原则

代码语言:javascript
复制
public class AddressBookService 
{
    public string GetPhoneNumber( string name )
    {
        if ( name is null )
            throw new ArgumentException( "name" );
        var entry = PhoneNumberDatabase.GetEntryByName( name );
        return(entry.PhoneNumber);
    }
}

虽然上面的代码阅读和维护都相当简单,但是它做了两件事:一是检查传入的name是否是有效的;二是基于传入的name找到电话号码。虽然检查参数的有效性和服务方法相关,但是它仍然是可以分离和复用的辅助功能。下面是使用AOP重构之后的伪代码:

代码语言:javascript
复制
public class AddressBookService
{
    [CheckForNullArgumentsAspect]
    public string GetPhoneNumber( string name )
    {
        var entry = PhoneNumberDatabase.GetEntryByName( name );
        return(entry.PhoneNumber);
    }
}
public class CheckForNullArgumentsAspect 
{
    public void OnEntry( MethodInformation method )
    {
        foreach ( arg in method.Arguments )
            if ( arg is null )
                throw ArgumentException( arg.name )
    }
} 

这个例子中的OnEntry方法多了个MethodInformation参数,它提供了一些关于方法的信息,为的是可以检测方法的参数是否为null。虽然这个方法微不足道,但是CheckForNullArgumentsAspect代码可以复用到确保参数有效的其他方法上。

代码语言:javascript
复制
public class AddressBookService
{
    [CheckForNullArgumentAspect]
    public string GetPhoneNumber( string name )
    {
        ...
    }
}
public class InvoiceService
{
    [CheckForNullArgumentAspect]
    public Invoice GetInvoiceByName( string name )
    {
        ...
    }


    [CheckForNullArgumentAspect]
    public void CreateInvoice( ShoppingCart cart )
    {
        ...
    }
}
public class PaymentSevice
{
    [CheckForNullArgumentAspect]
    public Payment FindPaymentByInvoice( string invoiceId )
    {
        ...
    }
}

这样一来,如果我们想要修改和Invoice相关的东西,只需要修改InvoiceService。如果想要修改和null检测相关的一些事情,只需要修改CheckForNullArgumentAspect。涉及到的每个类只有一个原因修改。现在我们就不太可能因为修改造成bug或倒退。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-06-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档