zoukankan      html  css  js  c++  java
  • 聊聊模板方法模式,装饰器模式以及AOP

    在软件系统设计的时候,我们需要把一个大的系统按照业务功能进行拆分,做到高内聚、低耦合。

    但是呢,拆分之后会产生一些通用性的东西,比如日志,安全,事务,性能统计等,这些非功能性需求,横跨多个模块。最low的一种写法如下:

    public class PlaceOrderCommand {
        public void execute(){
            //记录日志
            logger.debug("...");
            //性能统计
            PerformaceUtil.startTimer(...);
            //权限检查
            if(!user.hasPreviledge(...)){
                抛异常
            }
            //开始事务
            beginTransaction();
            
            //真正的业务代码
            
            commitTransaction();
            
            PerformanceUtil.endTimer(...);
            logger.debug("...");
        }
    }

    为什么说上面这种方式low呢,主要是因为它把日志、安全、事务、性能统计这些非业务相关的代码和业务代码完全耦合在了一起,每个类都得这么写,徒劳无功,繁琐重复。

    1. 模板方法模式

    由此引入一种牛逼的处理方式,叫做模板方法模式。在父类中将乱七八糟的非功能性代码写好,子类去实现相应的抽象方法就好啦。

    public abstract class BaseCommand{
        public void execute(){
            //记录日志
            logger.debug("...");
            //性能统计
            PerformaceUtil.startTimer(...);
            //权限检查
            if(!user.hasPreviledge(...)){
                抛异常
            }
            //开始事务
            beginTransaction();
    
            //真正的业务代码
            doBusiness();
    
            commitTransaction();
    
            PerformanceUtil.endTimer(...);
            logger.debug("...");
        }
        public abstract void doBusiness();
    }
    
    class PlaceOrderCommand extends BaseCommand {
        @Override
        public void doBusiness() {
            //下单操作
        }
    }
    
    class PaymentCommand extends BaseCommand {
        @Override
        public void doBusiness() {
            //执行支付操作
        }
    }
    
    //执行代码
    BaseCommand command = new PlaceOrderCommand();
    cmd.execute();

    子类只需要关注业务逻辑就好啦,是不是很清爽?

    但是这也有问题,就是我把非业务功能写了一大坨,我要是想分开描述,比如安全啊,事务啊,日志啊,我不想写在一起,就比较麻烦。我可以写多个抽象类,但是Java又是单类继承。而且,这样父类会定义一切,子类只能无条件接受,制约性比较大,比如有个子类不想加日志了,就没办法了。

    2. 装饰器模式

    由此引入更牛逼的一种设计模式,装饰器模式。它牛逼在可以灵活地自定义执行次序。

    public interface Command {
        public void execute();
    }
    
    //记录日志的装饰器
    class LoggerDec implements Command {
        Command cmd;
    
        public LoggerDec(Command cmd) {
            this.cmd = cmd;
        }
    
        @Override
        public void execute(){
            //记录日志
            logger.debug("...");
            //真正的业务代码
            this.cmd.execute();
            logger.debug("...");
        }
        
    }
    
    //性能统计的装饰器
    class PerformanceDec implements Command {
        Command cmd;
    
        public PerformanceDec(Command cmd) {
            this.cmd = cmd;
        }
    
        @Override
        public void execute(){
            PerformaceUtil.startTimer(...);
            //真正的业务代码
            this.cmd.execute();
            PerformanceUtil.endTimer(...);
        }
    
    }
    
    class PlaceOrderCommand implements Command {
        @Override
        public void execute() {
            //下单操作
        }
    }
    
    class PaymentCommand implements Command {
        @Override
        public void execute() {
            //支付操作
        }
    }
    
    //执行代码
    Command command = new LoggerDec(new PerformanceDec(new PlaceOrderCommand()));
    command.execute();

    可以随意更改包裹的内容,即代码中的红色区域,更改顺序啊,加一个减一个啊等等,灵活不已。

    但是也有一些问题,就是处理日志、安全、事务、性能统计的类为啥要实现业务接口(Command)呢?这在道理上面说不通啊。还有就是,没有实现业务接口,也想实现非业务功能,那应该怎么办呢?

    3. AOP

    AOP是Spring框架中很牛逼的一个组成,另外一个是IoC。把非功能性代码和业务代码完全隔离开来,让其正交。

    把业务代码看成面包,那么非功能性代码就是一个个切面,方便灵活切入,这就是面向切面编程。

    定义一个切面类,就是一个普通类。

    public class Transaction {
        public void begin(){
            //开始事务
        }
        public void commit(){
            //提交事务
        }
    }

    定义一个切入点,就是com.nonfunction包下面的所有类的execute()方法。

    定义一个通知,就是在方法调用之前执行啊,还是在方法调用之后执行啊。

    我们想做的就是,对于com.nonfunction包下面的所有类的execute()方法,在调用之前执行开始事务的方法,在调用之后执行提交事务的方法。

    这里以XML隔离为例(建议用注解的方式):

    <bean id="tx" class="com.nonfunction.Transaction" />  
    <aop:config>  
            <aop:aspect id="txAspect" ref="tx">  
                <aop:pointcut id="placeorder" expression="execution(public * com.nonfunction.*.execute(..))"/>                
                <aop:before method="begin"  pointcut-ref="placeorder"/>
    <aop:after method="commit" pointcut-ref="placeorder"/>
    </aop:aspect>
    </aop:config>

    以上的方式,便可以达到完全的隔离。

    但是呢,由于Java是静态强类型语言,编译成Java类以后,运行时通过反射可以查看类的信息,但是对编译好的类进行修改是不可能的。

    咋办?

    第一种方式就是修改现有类,在编译的时候把非功能性代码和业务代码编译到一起,这样改变业务类了,扑街。

    第二种方式生成代理类,这也是用的最多的,让代理类变成增强类,原来的业务类不用改变,客户操作的是代理类对象。

    那么怎么生成代理类呢,一种是使用Java自己的动态代理技术,一种是CGLib。

    参考我的这篇博客吧:https://www.cnblogs.com/DarrenChan/p/9958421.html

    所谓的Spring容器,就是帮你生成一个代理类,我们给它要对象,它就给你生成一个代理对象,但是我们不知道啊,还以为是原来业务类的对象。

  • 相关阅读:
    使用正则表达式验证邮箱格式
    写一个function,清除字符串前后的空格。(兼容所有浏览器)
    圣杯/双飞翼布局
    请指出document load和document ready的区别?
    Ajax的优缺点及工作原理?
    Web Storage与Cookie相比存在的优势:
    sessionStorage 、localStorage 和 cookie 之间的区别
    什么叫优雅降级和渐进增强?
    浏览器的内核分别是什么?
    [翻译] MSAlertController
  • 原文地址:https://www.cnblogs.com/DarrenChan/p/10343885.html
Copyright © 2011-2022 走看看