欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

扩展性改造--策略模式

程序员文章站 2022-05-04 20:42:15
...

引言

 

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

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

 

最近在做的项目是一个多产品融合项目,以前这几个产品是独立开发、部署、维护,但这些产品都有很多相似之处,比如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

  • 扩展性改造--策略模式
            
    
    博客分类: 设计模式 策略模式 
  • 大小: 12.3 KB
  • 扩展性改造--策略模式
            
    
    博客分类: 设计模式 策略模式 
  • 大小: 34.8 KB
相关标签: 策略模式