引言
相信很多人多看过“策略模式”的定义、类图关系、以及使用介绍,本文的标题是“扩展性改造--策略模式”,但不会一开始就对“策略模式”的定义、类图进行讲解。
本文通过一个笔者最近开发中遇到的真实案例进行讲解,来说明“策略模式”到底用来解决什么问题,或者说什么情况下该使用“策略模式”,让大家更清楚的理解“策略模式”。
最近在做的项目是一个多产品融合项目,以前这几个产品是独立开发、部署、维护,但这些产品都有很多相似之处,比如pc店铺、m店铺、pc活动、m活动(pc指的是电脑版,m指的是移动版)。但所有相似(或者相同)的功能要在多个系统中单独维护,这些相似的功能有变动时,每个系统都需要做对应的修改,复用性很差。这个多产品融合项目,其中一个项目目标是为了提高“复用性”。
扩展性问题
把多个产品融合到一个系统确实解决了我们大部分“相似组件”的复用性问题,最近几天对同事们的代码进行codeview,发现这种融合代码设计又引入的新的扩展性问题。先来看下现在的代码结构:
A、首先定义一个接口类(BaseService),该类包含定各个业务接口方法定义(对应pc店铺、m店铺、pc活动、m活动)
B、然后定义一个抽象类(BaseAbsServcie),该类实现了部分各个业务都相同的业务方法,并对各个业务实现不同的部分方法定义为abstract方法。
C、最后按业务定义4个不同的实现类:PcShopServiceImpl、MShopServiceImpl、PcActServiceImpl、MActServiceImpl。
类图关系如下:
乍一看没啥问题,思路很清晰,4个业务公共的操作提到BaseAbsServcie中实现,变动的业务放到4个业务实现类里实现,实现了“复用性”和“扩展性”的完美结合。真的“完美”了吗?来看几个问题:
1、如果有一个方法,只能部分复用:比如pc店铺和m店铺是相同的实现,pc活动和m活动实现是相同,但店铺跟活动实现有差别。那这个方法是放到各个实现类里、还是BaseAbsServcie里呢?如果放到实现类里,就无法复用;如果是放到BaseAbsServcie里,只能部分就得定义成两个方法,对应的在BaseService里也得定义成两个方法,但实际上只应该定义一个方法。
2、BaseAbsServcie中定义的是公共方法,如果有一天这个公共方法店铺的需要调整,而活动业务的不变(或者pc业务的需要调整,而m业务的不变),怎么处理?在BaseAbsServcie中拆成两个方法,还是该到各个业务实现方法中去重写,不管采用哪种方式,对于后期维护都是灾难性的。
3、假设有一天业务扩展了,新增两个微信、手Q业务,又该怎么办?
打住。。。我已经不知道将来该如何维护这套代码了。
那应该如何来改进呢?其实这是一个非常普遍的问题,在java编程的世界里,已经有很多人遇到过类似的问题,并已经有很好的解决方案形成一种编程模式,大家只要采用这种模式进行处理即可。
OO设计原则
为了解决上述问题,我们先来看下两条“OO设计原则”:1、针对接口编程、不针对实现编程;2、多用组合,少用继承。
上述问题的根本原因,就是违背了这两条设计原则。上述方案的本质是上面向实现编程,对每个业务创建一个实现类;在复用性上采用的是继承实现,而非组合。
什么是“面向接口编程”,什么是“组合”?我们先来看下新的设计方案:
1、分析该服务里包含的公共“行为”:页面渲染、缓存处理。定义两个行为接口类: RenderBehavior、CacheBehavior。
2、创建BaseService服务接口类,并创建一个抽象的实现类BaseAbsServcie,通过“组合”的方式,把RenderBehavior、CacheBehavior定义为BaseAbsServcie的成员变量。
3、定义“页面渲染行为”RenderBehavior的实现,根据业务分为pc页面渲染和m页面渲染,分别创建接口实现类:PcRenderImpl(pc活动和pc店铺都属于pc渲染)、MRenderImpl(m活动和m店铺都属于m渲染),
4、定义“缓存处理行为”CacheBehavior的实现,根据业务分为redis缓存和硬盘存储,分别创建接口实现类:CacheRedisImpl、 CacheDiskImpl (无缓存实现CacheNoImpl)。
5、重新定义抽象服务类BaseAbsServcie的4个业务子类:MActServiceImpl、MShopServiceImpl、PcActServiceImpl、PcShopServiceImpl
由于代码内容较多,这里没有把具体的代码内容贴出来,具体代码内容详见github:https://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
策略模式
其实上述优化过程就是使用的“策略模式”,其核心就是:抽取类中的所有“行为”(可以有不同算法实现),并为可变的“行为”定义接口,为每个接口创建不同的算法实现(称之为“面向接口编程”);并把这些“行为接口”作为“成员变量”引入到策略类(称之为“组合”);在策略类的子类中,根据业务需要选择指定的算法实现进行初始化。
以上述代码为例:
“行为接口”类有两个:RenderBehavior、CacheBehavior;
“行为算法实现”类有5个,对于上面两个行为接口:
PcRenderImpl、MRenderImpl、CacheRedisImpl、CacheNoImpl、CacheDiskImpl;
“策略类”为:BaseAbsServcie,行为接口作为其成员变量;
“策略类的子类,对应4个具体的业务:
PcShopServiceImpl、PcActServiceImpl、MShopServiceImpl、MActServiceImpl。
策略模式可以在不影响已有“行为”算法的情况下,实现对 “行为”新算法的无限扩展,以及算法的动态切换,并且不会影响客户端代码。从而是代码框架具备良好的扩展性和维护性(修改其中一个算法,不会影响到其他算法)。
策略模式遵循上述提到的设计原则: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
相关推荐
行为型模式则关注对象之间的交互和职责分配,如策略模式(Strategy)、观察者模式(Observer)和责任链模式(Chain of Responsibility)等,它们有助于提高代码的灵活性和可扩展性。 在C#中,设计模式的实现往往...
在可扩展性设计的实践案例中,新浪DBA团队很可能根据数据流量和访问模式设计了相应的Sharding策略。Sharding是指将数据分布到多个数据库服务器上,每个服务器上只有数据库的一部分数据,从而使得数据库能够水平扩展...
在这种场景下,设计模式为我们提供了解决方案,其中之一就是策略模式。策略模式允许我们将每一种策略(即每一种筛查模式)封装成一个独立的类,从而避免if-else结构的膨胀。 在给定的需求中,交易系统需要根据不同...
【一级分行局域网改造技术...总的来说,这个技术方案全面地规划了一级分行局域网的改造,涵盖了从架构设计、设备选型到安全配置的各个环节,旨在构建一个高效、安全、可扩展的网络环境,以支持中国建设银行的业务发展。
- 考虑设备兼容性与可扩展性。 - **网络架构优化**: - 采用SDN等先进技术。 - 加强网络安全策略。 - **供电系统改造**: - 双路供电、UPS等措施。 - 加强监控与管理。 - **新技术与管理手段引入**: - 自动化...
"源码"标签暗示着改造过程中可能涉及到对系统源代码的深入理解和修改,这可能包括阅读和分析现有的代码逻辑,找出瓶颈,改进算法,或者引入新的设计模式来提升系统的可维护性和扩展性。"工具"标签则可能意味着在改造...
设计模式是软件开发中的一种重要思想,用于解决常见的设计问题,提高代码的可维护性和可扩展性。在《深入浅出设计模式》的学习笔记中,我们可以看到几种关键的设计模式及其应用,包括策略模式、观察者模式、装饰者...
观察者模式允许多个对象监听这些事件并做出响应,提高了代码的灵活性和可扩展性。 接着,"策略模式"可能用于处理不同的绘图工具。例如,画笔、橡皮擦、选择工具等都可以看作是不同的策略,根据用户的选择动态地改变...
总结来说,该改造方案旨在通过升级核心设备、优化AP部署策略以及引入集中管理技术,大幅提升WLAN无线网络的性能、稳定性和安全性,以适应当前和未来可能的增长需求,同时简化运维工作,降低总体拥有成本。...
在讲解完经典设计模式后,作者王翔还会讨论如何根据实际项目需求,对这些模式进行扩展和改造。这包括如何将设计模式与其他技术(如.NET框架、WCF、WF等)结合,以及如何在面向服务架构(SOA)和微服务架构中应用设计...
在控制策略的实施过程中,还需要考虑到系统的兼容性和扩展性,为未来可能的技术升级留出空间。 总之,煤矿井下排水系统的自动化改造和控制策略优化不仅能够提高生产效率和安全性,还能够降低能源消耗和人工成本,为...
- 提高了系统的复用性和扩展性。 - 可以解决因接口不匹配而导致的问题。 **缺点:** - 适配器模式可能引入过多的对象,增加了系统的复杂性。 - 如果过度使用适配器模式,可能会导致系统难以理解和维护。 ##### 7. ...
总结来说,城市核心更新改造地区的用地与交通协同发展模式的探索是一项复杂而关键的任务,它需要在土地高效利用和交通系统升级之间找到平衡,兼顾经济效益、社会公平和环境可持续性。通过科学研究和实践,我们可以为...
通过以上改造策略,我们可以使Struts 1在现有项目中继续发挥价值,同时逐步向更先进的框架过渡。当然,对于长远的规划,考虑迁移到更现代的框架可能是更好的选择,但这需要权衡成本和收益,以及系统的升级路径。
- 扩展性:设计具备良好的扩展性和灵活性,适应未来需求。 - 管理性:建立完善的运营、管理和维护机制。 3. 设计内容 建议采用Cisco的网络改造解决方案,分析现有局域网,确保改造后的无缝切换。设计包括目标...
2. **后期改造成本高**:如果一开始没有考虑到可扩展性,后期进行系统改造的成本将会非常高昂。 3. **价格性能曲线**:随着系统规模的增长,单纯通过增加资源的方式提升性能的成本会越来越高。 #### 复制结构 ...
网络改造应着眼于未来业务需求,明确要运营的具体业务类型(如网页浏览、VOIP、VOD等),并根据不同业务的特点优化网络结构,提高网络的可靠性、可维护性和可扩展性。 #### 六、广电HFC双向改造技术方案 1. **传统...
微服务设计模式大全详解涵盖了微服务架构中的一些关键设计策略,旨在提升系统的可伸缩性、灵活性、故障隔离和可见性。以下是这些模式的详细解释: 1. **分解模式**: - **按业务能力分解**:遵循单一职责原则,将...