策略模式与案例运用
策略模式
写在前面
策略模式不是解决if else 的,不是用于分支选择的。
策略模式的前提是已知所选哪个分支。
在我理解看来策略模式其实只是使得代码更加整洁,合理。或者是说让业务代码(客户端代码context)与算法(具体的实现or strategy)隔离开来。
一、策略模式的用法
(一)策略模式有3个东西:
- context (调用策略接口方法的环境类)他会维护一个Strategy的引用,用于调用实际的接口方法
- strategy (策略接口)定义支持所有算法的公共接口
- XXStrategy (具体策略实现类)实现Strategy接口中的具体算法
(二)案例解说
核销平台接入若干个业务方,我们需要要某些场景调用业务方提供的pigeon service
例如:我需要调用业务方的根据id查询商品信息的方法,但是各个业务方的实现不同。我需要根据商品类型调用不同的业务方方法。
改造前
代码(一)根据id查找商品信息
public getProductInfoByProductIdAndType(Long productId, Integer type) {
if (type == 1) {
//丽人BU
beautyBiz.getProductInfoByProductId(productId);
} else if (type == 2) {
//休娱BU
entertainBiz.getProductInfoByProductId(productId);
} else {...}
}
以上是之前的代码,且除了这个方法以外,还有很多类似的其他的方法。可以看出算法与业务耦合在一起了。
改造后
根据策略模式的思路,新建了一个strategy接口,再新建了2个具体实现策略类(假设目前有2个业务方接入)。其实还可以写一个AbstractBaseStrategy的抽象类实现Strategy接口,实现所有接口方法的默认实现,好处有二:1、提取公共代码 2、有些业务方,没有提供某些接口方法
代码(二)具体策略类
//丽人策略类
public class BeautyStrategy implements Strategy {
@Override
public ProductDTO getProductByIdAndType(Long aLong) {
return null;
}
@Override
public ProductDTO getProductByIdAndVersion(Long aLong, String s) {
return null;
}
。。。
}
//休娱策略类
public class EntertainStrategy implements Strategy {
@Override
public ProductDTO getProductByIdAndType(Long aLong) {
return null;
}
@Override
public ProductDTO getProductByIdAndVersion(Long aLong, String s) {
return null;
}
}
那么此时context又如何写呢?其实跟之前差不多,可能看起来整洁些了。
代码(三)context环境类
public class context {
public ProductDTO getProductInfoByProductIdAndType (Long productId, Integer type) {
Strategy strategy = null;
if (type == 1) {
strategy = new BeautyStrategy();
} else if (type == 2) {
strategy = new EntertainStrategy();
}
strategy.getProductById(productId);
}
...
}
或者还可以用spring bean的方式事先装配好这些service的对象。
如何去掉if else 呢?
(一)通过HashMap
首先准备好type与strategy的对应关系Map。
这个就有多种方法:
1、如果使用spring,那么就需要在context里写一个map,这个map可以在xml里配置,在成员变量处自动装配;或者在构造函数里装配,但是依据spring版本的不同,构造函数是否传入参数有异议。以下配置是xml:
<bean id="typeStrategyMap" class="java.util.HashMap">
<constructor-arg>
<map key-type="java.lang.Integer">
<entry key="1" value-ref="beautyStrategy"/>
<entry key="4" value-ref="beautyStrategy"/>
</map>
</constructor-arg>
</bean>
前提是beautyStrategy也得声明好bean,可通过注解或者是beanxml。
这时,context代码如下写法:
@Resource
private Map<Integer, Strategy> typeStrategyMap;
public ProductDTO getProductByIdAndType(Long productId, int type) {
if (productId == null) {
return null;
} else {
Map<Integer, Strategy> typeStrategyMap = this.getTypeStrategyMap();
//校验类型
if (!this.typeValidate(typeStrategyMap, type)) {
return null;
} else {
ProductDTO productDTO = null;
//通过具体策略调用相应业务方的方法
Strategy strategy = (Strategy)typeStrategyMap.get(type);
if (strategy != null) {
productDTO = strategy.getProductByIdAndType(productId);
}
return null != productDTO && type == productDTO.getProductType().intValue() ? productDTO : null;
}
}
}
说白了,很简单,就是用map.get方法来去掉所有的if else
(二)通过枚举类 + 工厂方式
枚举类的用法与map有些许差异,但是本质是一样的,将type与具体的策略关联起来。
1、枚举类
待测试
public enum TypeStrategyEnum {
beauty_strategy(1, new TestStrategy());
private int type;
private Strategy strategy;
TypeStrategyEnum(int type, Strategy strategy) {
this.type = type;
this.strategy = strategy;
}
}
public enum TypeStrategyEnum {
beauty_strategy(1, "beautyStrategy");
private int type;
private String strategy;
TypeStrategyEnum(int type, String strategy) {
this.type = type;
this.strategy = strategy;
}
}
2、context
(1)利用反射,来动态创建具体的策略对象
public class Context{
public ProductDTO getProductInfoByProductIdAndType(Long productId, Integer type) {
//根据type拿到枚举类
TypeStrategyEnum typeStrategyEnum = TypeStrategyEnum.getEnumByType(type);
//利用工厂
Strategy strategy = (Strategy) Class.forName(typeStrategyEnum.getStrategy()).newInstance();
strategy.getProductByIdAndType(productId);
}
}
(2)通过java代码获取bean
获取bean的几种方式
譬如说可以在代码里读取xml配置,然后根据名称拿到bean对象,或者通过其他方式。。。