扩展性改造--策略模式

引言

 

相信很多人多看过策略模式的定义、类图关系、以及使用介绍,本文的标题是扩展性改造--策略模式,但不会一开始就对“策略模式”的定义、类图进行讲解。

本文通过一个笔者最近开发中遇到的真实案例进行讲解,来说明策略模式到底用来解决什么问题,或者说什么情况下该使用“策略模式”,让大家更清楚的理解“策略模式”。

 

最近在做的项目是一个多产品融合项目,以前这几个产品是独立开发、部署、维护,但这些产品都有很多相似之处,比如pc店铺、m店铺、pc活动、m活动(pc指的是电脑版,m指的是移动版)。但所有相似(或者相同)的功能要在多个系统中单独维护,这些相似的功能有变动时,每个系统都需要做对应的修改,复用性很差。这个多产品融合项目,其中一个项目目标是为了提高复用性

 

扩展性问题

 

把多个产品融合到一个系统确实解决了我们大部分“相似组件”的复用性问题,最近几天对同事们的代码进行codeview,发现这种融合代码设计又引入的新的扩展性问题。先来看下现在的代码结构:

A、首先定义一个接口类(BaseService),该类包含定各个业务接口方法定义(对应pc店铺、m店铺、pc活动、m活动)

B、然后定义一个抽象类(BaseAbsServcie),该类实现了部分各个业务都相同的业务方法,并对各个业务实现不同的部分方法定义为abstract方法。

C、最后按业务定义4个不同的实现类:PcShopServiceImplMShopServiceImplPcActServiceImplMActServiceImpl

类图关系如下:




乍一看没啥问题,思路很清晰,4个业务公共的操作提到BaseAbsServcie中实现,变动的业务放到4个业务实现类里实现,实现了复用性扩展性的完美结合。真的完美了吗?来看几个问题:

 

1、如果有一个方法,只能部分复用:比如pc店铺和m店铺是相同的实现,pc活动和m活动实现是相同,但店铺跟活动实现有差别。那这个方法是放到各个实现类里、还是BaseAbsServcie里呢?如果放到实现类里,就无法复用;如果是放到BaseAbsServcie里,只能部分就得定义成两个方法,对应的在BaseService里也得定义成两个方法,但实际上只应该定义一个方法。

 

2BaseAbsServcie中定义的是公共方法,如果有一天这个公共方法店铺的需要调整,而活动业务的不变(或者pc业务的需要调整,而m业务的不变),怎么处理?在BaseAbsServcie中拆成两个方法,还是该到各个业务实现方法中去重写,不管采用哪种方式,对于后期维护都是灾难性的。

 

3、假设有一天业务扩展了,新增两个微信、手Q业务,又该怎么办?

 

打住。。。我已经不知道将来该如何维护这套代码了。

那应该如何来改进呢?其实这是一个非常普遍的问题,在java编程的世界里,已经有很多人遇到过类似的问题,并已经有很好的解决方案形成一种编程模式,大家只要采用这种模式进行处理即可。

 

OO设计原则

 

为了解决上述问题,我们先来看下两条“OO设计原则”:1、针对接口编程、不针对实现编程;2、多用组合,少用继承。

 

上述问题的根本原因,就是违背了这两条设计原则。上述方案的本质是上面向实现编程,对每个业务创建一个实现类;在复用性上采用的是继承实现,而非组合。

 

什么是“面向接口编程”,什么是“组合”?我们先来看下新的设计方案:

 

1、分析该服务里包含的公共行为:页面渲染、缓存处理。定义两个行为接口类: RenderBehaviorCacheBehavior

2、创建BaseService服务接口类,并创建一个抽象的实现类BaseAbsServcie,通过组合的方式,把RenderBehaviorCacheBehavior定义为BaseAbsServcie的成员变量。

3、定义“页面渲染行为”RenderBehavior的实现,根据业务分为pc页面渲染和m页面渲染,分别创建接口实现类:PcRenderImplpc活动和pc店铺都属于pc渲染)、MRenderImplm活动和m店铺都属于m渲染),

4、定义“缓存处理行为”CacheBehavior的实现,根据业务分为redis缓存和硬盘存储,分别创建接口实现类:CacheRedisImpl CacheDiskImpl (无缓存实现CacheNoImpl)

5、重新定义抽象服务类BaseAbsServcie4个业务子类:MActServiceImplMShopServiceImplPcActServiceImplPcShopServiceImpl

由于代码内容较多,这里没有把具体的代码内容贴出来,具体代码内容详见githubhttps://github.com/gantianxing/strategy.git

 

最终的类图如下:



 

可以看到,该方案通过组合的方式把抽取的渲染行为缓存处理行为整合到BaseAbsServcie,而不是通过继承。并把面向业务的的子类实现,改为面向渲染行为缓存处理行为接口的实现,这就是前面提到的面向接口编程

 

假设现在要增加一个微信手q”业务页面渲染,并且不能cdn缓存。这时我们只需要新增一个“WqRenderImpl”,缓存处理复用CacheNoImpl,再创建一个BaseAbsService的子类WqServiceImpl即可完成业务扩展,并且不会对已有代码造成任何影响。

 

最后编写测试方法:

public class Test {
    public static void main(String[] args) {
        MActServiceImpl mact = new MActServiceImpl(new MRenderImpl(),new CacheRedisImpl());
        //mact.setCacheBehavior(new CacheNoImpl());//动态调整缓存行为
        String pageId="sdfsdfsfd";
        mact.render(pageId);
    }
}

 

运行github中的代码,打印消息如下:

 
设置m活动页缓存key
设置m活动页cdn缓存URL
m页面渲染
第一步:采用redis缓存m_act_page_sdfsdfsfd
第二步:清除cdn缓存sale.jd.com/m/act/sdfsdfsfd.html

 

策略模式

 

其实上述优化过程就是使用的策略模式,其核心就是:抽取类中的所有行为(可以有不同算法实现),并为可变的行为定义接口,为每个接口创建不同的算法实现(称之为面向接口编程);并把这些“行为接口”作为“成员变量”引入到策略类(称之为组合);在策略类的子类中,根据业务需要选择指定的算法实现进行初始化。

 

以上述代码为例:

行为接口类有两个:RenderBehaviorCacheBehavior

行为算法实现类有5个,对于上面两个行为接口:

PcRenderImplMRenderImplCacheRedisImplCacheNoImplCacheDiskImpl

策略类为:BaseAbsServcie,行为接口作为其成员变量

“策略类的子类,对应4个具体的业务:

PcShopServiceImplPcActServiceImplMShopServiceImplMActServiceImpl

 

策略模式可以在不影响已有行为算法的情况下,实现对 行为新算法的无限扩展,以及算法的动态切换,并且不会影响客户端代码。从而是代码框架具备良好的扩展性和维护性(修改其中一个算法,不会影响到其他算法)。

 

策略模式遵循上述提到的设计原则:1、针对接口编程、不针对实现编程;2、多用组合,少用继承

 

spring中使用策略模式

 

上述测试代码是在main方法中运行,但我们现实中大多数情况下都是使用的spring框架。最后来看下在spring中如何使用策略模式。

 

首先创建一个工厂类,具体代码内容如下:

public class MySpringFactory {
    private Map<String, BaseAbsServcie> servcieMap = new HashMap();
 
    public Map<String, BaseAbsServcie> getServcieMap() {
        return servcieMap;
    }
 
    public void setServcieMap(Map<String, BaseAbsServcie> servcieMap) {
        this.servcieMap = servcieMap;
    }
 
    public void doRender(String strType,String pageId) {
        this.servcieMap.get(strType).render(pageId);
    }
}

然后定义spring bean xml配置文件(java配置方式可以自行实现):

<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="byName">
 
    <bean id="mySpringFactory" class = "com.sky.strategy.MySpringFactory">
        <property name="servcieMap">
            <map>
                <entry key="pcAct" value-ref="pcAct"/>
                <entry key="mAct" value-ref="mAct"/>
                <entry key="pcShop" value-ref="pcShop"/>
                <entry key="mShop" value-ref="mShop"/>
            </map>
        </property>
    </bean>
 
    <bean id="pcAct" class="com.sky.strategy.service.impl.PcActServiceImpl">
        <constructor-arg name="renderBehavior" ref="pcRender"/>
        <constructor-arg name="cacheBehavior" ref="cacheRedis"/>
    </bean>
 
    <bean id="mAct" class="com.sky.strategy.service.impl.MActServiceImpl">
        <constructor-arg name="renderBehavior" ref="mRender"/>
        <constructor-arg name="cacheBehavior" ref="cacheRedis"/>
    </bean>
 
    <bean id="pcShop" class="com.sky.strategy.service.impl.PcShopServiceImpl">
        <constructor-arg name="renderBehavior" ref="pcRender"/>
        <constructor-arg name="cacheBehavior" ref="cacheDisk"/>
    </bean>
 
    <bean id="mShop" class="com.sky.strategy.service.impl.MShopServiceImpl">
        <constructor-arg name="renderBehavior" ref="mRender"/>
        <constructor-arg name="cacheBehavior" ref="cacheDisk"/>
    </bean>
 
    <bean id="pcRender" class="com.sky.strategy.behavior.impl.PcRenderImpl"/>
    <bean id="mRender" class="com.sky.strategy.behavior.impl.MRenderImpl"/>
 
    <bean id="cacheDisk" class="com.sky.strategy.behavior.impl.CacheDiskImpl"/>
    <bean id="cacheNo" class="com.sky.strategy.behavior.impl.CacheNoImpl"/>
    <bean id="cacheRedis" class="com.sky.strategy.behavior.impl.CacheRedisImpl"/>
 
</beans>

在需要使用的地方直接引用MySpringFactory对应的spring bean即可:

private MySpringFactory mySpringFactory;
 
    public void commonRender(String type,String pageId){
        mySpringFactory.doRender(type,pageId);
    }
 

 

代码详见github: https://github.com/gantianxing/strategy.git

猜你喜欢

转载自moon-walker.iteye.com/blog/2390894