Strategy 策略模式 在 Spring 开发中的应用 包括事务管理,日志管理(logger),IoC容器根据bean定义的内容实例化等

来源:互联网 发布:ubuntu安装时合并分区 编辑:程序博客网 时间:2024/06/03 03:33

具体Strategy模式介绍见 Strategy模式详解

Strategy模式的本意是封装一系列可以互相替换的算法逻辑,使得具体算法的演化独立于使用它们的客户端代码。 为了理解为什么要这么做,我们不妨来看一个具体的场景:

在一个信贷系统中,通常会提供多种还款方式,比如等额本金还款方式,等额本息还款方式,一次还本付息方式等等, 那么,针对每一位顾客所选择的还款方式,我们就需要按照这些还款方式的具体逻辑为顾客计算每次所需要归还的本金以及利息的额度, 如果要我们来实现这个根据还款方式计算额度的逻辑,我们会怎么做那?

对于谙熟结构化编程或者面向对象编程不甚娴熟的开发人员来说,他们可能会直接使用多重条件语句来实现这段计算逻辑:
public RepaymentDetails calculateRepayment(BigDecimal totalAmount,String customerId){RepaymentDetails details = new RepaymentDetails();Object type =  getRepaymentTypeByCustomerId(customerId);if(isEqualInterestRepaymentType(type)){BigDecimal interest = getEqualInterestOfCentrelBank();YearMonthDay repaymentInterval = getRepaymentIntervalByCustomerId(customerId);// carry out caculation according to totalAmount and other data}if(isEqualPrincipalRepaymentType(type)){BigDecimal interest = getStandardInterestOfCentrelBank();YearMonthDay repaymentInterval = getRepaymentIntervalByCustomerId(customerId);// carry out caculation according to totalAmount and other data}if(isOnceForAll(type)){BigDecimal interest = getStandardInterestOfCentrelBank();// carry out caculation according to totalAmount and other data}...return details;}

当然,你可以对这些代码做进一步的改进,但是,如果总体结构上不做任何变更的话,这种实现方式暴露的问题会依然存在:
  • 客户端代码与算法逻辑代码相互混杂, 导致客户端代码的过于复杂并且后期难以维护;

  • 混杂的算法逻辑代码与客户端代码耦合性太强,算法的变更或者添加新的算法都会直接导致客户端代码的调整,使得客户端代码和算法逻辑代码无法独立演化;

  • 几乎同一逻辑单元内实现的各种算法无可避免的需要多重的条件语句来区分针对不同算法所使用的数据或者对应算法的特定逻辑实现;

所以,该是Strategy模式登场的时间啦!

 

使用Strategy模式来重构这段代码的话,我们首先通过RepaymentStrategy定义来抽象还款逻辑算法, 然后,针对不同的还款方式,给出RepaymentStrategy定义的不同实现。对于使用还款逻辑的客户端代码来说, 它只需要获取相应的RepaymentStrategy引用,并调用接口暴露的计算接口即可:

Figure 1.18. RepaymentStrategy场景图

RepaymentStrategy场景图

客户端代码只需要跟策略接口打交道,而算法的变更以及添加对于使用策略接口进行计算操作的客户端代码来说几乎没有任何影响。

 

Strategy模式虽然定义上强调的是对算法的封装,但我们不应该只着眼“算法”一词, 实际上, 只要能够有效的剥离客户端代码与特定关注点之间的依赖关系,Strategy模式就应该进入考虑之列,在这一点上, spring框架的事务抽象就是一个很好的范例,通过将使用不同事务管理API进行事务管理的界定行为进行统一的抽象, 客户端代码可以透明的方式使用PlatformTransactionManager这一策略接口进行事务界定,即使具体的事务策略需要变更, 对于客户端代码来说也不会造成过大的冲击。


spring框架中使用Strategy模式的地方很多,除了本章的事务抽象框架,还包括以下几处:

IoC容器根据bean定义的内容实例化相应bean对象的时候,会根据情况决定使用反射还是使用cglib来实例化相应的对象。 InstantiationStrategy是容器使用的实例化策略的抽象接口,spring框架默认提供了 CglibSubclassingInstantiationStrategy和SimpleInstantiationStrategy两个具体实现 类。


spring的validation框架中,org.springframework.validation.Validator定义也是 一个策略接口, 具体的实现类将根据具体场景提供不同的验证逻辑,而这些具体验证逻辑的差异性,对于使用Validator进行数据验证的客户端代码来说,则是透明的。

除了在spring框架内大量使用Strategy模式,我们也可以在其他的框架设计中发现Strategy模式的影子,比如我们最常用的jakarta commons logging中,Log接口就是一个策略接口, Jdk14Logger,Log4JLogger以及SimpleLog等都是具体的策略实现类。 可见,只要针对同一件事情有多种选择的时候,我们都可以考虑用Strategy模式来统一一下抽象接口,为客户端代码“造福”。

 

Strategy模式的重点在于通过统一的抽象向客户端屏蔽其所依赖的具体行为,但该模式并没有关注客户端代码应该如何来使用这些行为。 一般来讲,客户端代码使用Strategy模式的方式可以简单划分为两种:

客户端整个生命周期内只依赖于单一的策略.  Spring提供的事务抽象可以归属这一类情况。使用PlatformTransactionManager进行事务界定的客户端代码在其整个生命周期内 只依赖于一个PlatformTransactionManager的实现类, 或者DataSourceTransactionManager,或者HibernateTransactionManager等等,这样的情况比较容易 处理,直接为客户端代码注入所需要的策略实现类即可。

客户端整个生命周期内可能动态的依赖多个策略.  比如我们的还款场景中,客户端可能需要根据每一个顾客所选择的还款方式来决定使用哪一个策略实现类为其计算对应的还款明细, 对于这种情况,你会发现,Strategy模式通常宣称的可以避免多重条件语句的问题其实仅仅是将其转移给了客户端代码而已:

  • RepaymentStrategy strategy = fallbackStrategy();    ...    public RepaymentDetails calculateRepayment(BigDecimal totalAmount,String customerId)    {    Object type =  getRepaymentTypeByCustomerId(customerId);    if(isEqualInterestRepaymentType(type))    {    strategy = EqualInterestStrategy();    }    if(isEqualPrincipalRepaymentType(type))    {    strategy = EqualPrincipalStrategy();    }    if(isOnceForAll(type))    {    strategy = OnceForAllStrategy();    }    ...    return strategy.performCalculation();    }

    不过,如果你想真正的避免多重条件语句的话,也不是没有办法,最简单的方法就是提前准备一个具体策略类型与其对应条件之间的关系映射。 对于还款的场景来说,我们可以这么做:
    1. 在客户端代码中声明一个对关系映射的依赖:

      public class StrategyContext        {        private Map<Object,RepaymentStrategy> strategyMapping;        ...        // setters and getters        }
    2. 通过ioc容器注入所有客户端可能动态依赖的策略实现类实例:

      <bean id="strategyContext" class="...StrategyContext">        <property name="strategyMapping">        <ref local="strategyMapping"/>        </property>        </bean>        <util:map id="strategyMapping">        <entry key="EQAUL_INTEREST">        <bean class="...EqualInterestStrategy"></bean>        </entry>        <entry key="EQAUL_PRINCIPAL">        <bean class="...EqualPrincipalStrategy"></bean>        </entry>        <entry key="ONCE_FOR_ALL">        <bean class="...OnceForAllStrategy"></bean>        </entry>        </util:map>

    3. 在计算还款明细的方法中,使用还款策略的代码直接从关系映射中获取具体的策略即可:

      RepaymentStrategy strategy = fallbackStrategy();        ...        public RepaymentDetails calculateRepayment(BigDecimal totalAmount,String customerId)        {        Object type =  getRepaymentTypeByCustomerId(customerId);        RepaymentStrategy strategy = strategyMapping.get(type);        // check constraint        if(strategy == null)        stargety = fallbackStrategy();        return strategy.performCalculation();        }

       

    Tip

    除了使用ioc容器注入映射关系,你还可以将对应应用程序的映射关系放到数据库或者其他外部配置文件,甚至Annotation中。 通过ioc容器一次注入多个策略实例可能需要占用多一些的系统资源,对于资源紧要的应用来说,可以考虑通过反射等方式按需构建具体策略实例,这个就留给读 者来完成吧!

 

在系统中合理的使用Strategy模式可以使得系统向着“高内聚,低耦合”的理想方向迈进,在改善应用程序代码结构的同时,进一步的提高产品质量。 实际上,Strategy模式更是多态(Polymorphism)的完美体现,当你的OO内功修炼到“炉火纯青”之地的时候,你也就发现,所谓的Strategy模式的概念,在你的脑海中或许已经淡然了。




0 0
原创粉丝点击