设计模式之策略模式(如何优雅的去除if-else逻辑代码)
项目要求:
新增一个数据接收的接口,接收来自不同渠道的数据进行个性化的处理。目前已知的渠道有360、百度,以后还会不断扩展渠道,要求必须在同一个接口处理所有数据接收请求。
大多数的实现是这样的:
/**
* 点击数据匹配接口
*/
@GetMapping(value = "/matchData", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public JsonResult matchData(@RequestParam("imei") String imei, @RequestParam("event_type") String event_type, @RequestParam("channelid") String channelid) {
try {
Map<String,Object> map=new HashMap<String,Object>(2);
map.put("imei", imei);
map.put("event_type", event_type);
if("01".equals(channelid)) {
do360MatchData(map);
}else if("02".equals(channelid)) {
doBaiduMatchData(map);
}else {
doSomethingElse(map);
}
return JsonResult.getSuccess("点击数据匹配");
} catch (Exception e) {
logger.error("点击数据匹配失败:" + e.getMessage(), e);
return JsonResult.getInnerFail("9999", "点击数据匹配失败");
}
以上代码实现有如下的缺点:
- 不易于扩展,增加一个新的渠道需要改变原有的代码,不符合开放封闭原则
- 使用多重条件选择语句,不符合面向对象设计思想
- 代码柔和在一起,不利于阅读和维护,不符合单一职责原则。
针对以上代码的缺点我们有啥好的改进方式吗?
有!我们可以利用策略模式来消除臃肿复杂的if-else。
策略模式定义
定义了一些平行的算法组,分别封装起来,算法之间可以相互替换,此模式使算法的变化独立于调用者之外
算法结构
- 抽象策略角色(Strategy):这是一个抽象类或者接口,将算法的行为进行封装,所有的策略类都要实现该接口
- 具体策略角色(ConcreteStrategy):封装了具体的算法和行为
- 环境角色(Context):持有一个抽象策略的引用,并提供统一调用的入口
代码实现:
1:首先定义抽象的数据策略接口
package com.zte.service.primary;
import java.util.Map;
public interface IMatchServcie {
void match(Map<String,Object> map);
String getChannelId();
}
其中 match方法是执行具体匹配数据逻辑的接口,而getChannelId方法则有妙用,请往下看
2:定义具体策略角色360数据匹配角色
package com.zte.service.primary;
import java.util.Map;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSONObject;
import com.zte.model.TAdvertisementMatching;
import com.zte.model.TAdvertisementThrowin;
import com.zte.utils.DateUtil;
@Service("match360Service")
public class Match360ServiceImpl implements IMatchServcie {
@Autowired
private Logger logger;
@Autowired
private RestTemplate restTemplate;
@Autowired
private AdvertisementThrowinService advertisementThrowinService;
public static final String CHANNEL360="22017001";
@Override
public void match(Map<String, Object> map) {
try {
doSomthing();
} catch (Exception e) {
logger.error("点击数据匹配失败:" + e.getMessage(), e);
}
}
@Override
public String getChannelId() {
return CHANNEL360;
}
}
该类实现了抽象策略接口,实现了默认的方法,其中match方法实现自己的业务逻辑,getChannelId方法返回一个具体的常量,还记得我们的需求吗?根据不同的渠道实现不同的数据匹配逻辑。我们如何实现,就是通过这个常量来匹配到具体的策略角色去处理。
3:环境角色类创建
package com.zte.service.primary;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class MatchStrategyPattern {
@Resource
private List<IMatchServcie> matchServcieList;
public void match(String channelId,Map<String,Object> map) {
Optional<IMatchServcie> optional = matchServcieList.stream()
.filter(service -> service.getChannelId().equals(channelId))
.findFirst();
optional.ifPresent(op->{optional.get().match(map);});
}
}
从上述代码我们可以看到,我们利用spring的自动注入,注入了一个类型位抽象策略接口的List,程序会自动将类型为IMatchService的具体策略角色类注入到List中。而match方法则是暴露给外部的统一匹配方法。该方法通过外部传入的渠道id字段通过接口方法getChaennlId返回的渠道id进行匹配,如果匹配中了就选用该角色进行逻辑处理。达到动态匹配的效果。
4:这个时候如果我们要加入一个新的数据匹配角色,我们只需要新建一个新类实现抽象策略方法,然后的然后程序就可以自动的跑起来,无需修改任何现有的文件。程序中也没有了那个令人烦厌的if-else。
package com.zte.service.primary;
import java.util.Map;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSONObject;
import com.zte.model.TAdvertisementMatching;
import com.zte.model.TAdvertisementThrowin;
import com.zte.utils.DateUtil;
@Service("matchBaiduService")
public class MatchBaiduServiceImpl implements IMatchServcie {
@Autowired
private Logger logger;
@Autowired
private RestTemplate restTemplate;
@Autowired
private AdvertisementThrowinService advertisementThrowinService;
public static final String CHANNELBAIDU="22017002";
@Override
public void match(Map<String, Object> map) {
try {
doSomthing();
} catch (Exception e) {
logger.error("点击数据匹配失败:" + e.getMessage(), e);
}
}
@Override
public String getChannelId() {
return CHANNELBAIDU;
}
}
策略模式的优缺点
优点
- 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
- 避免使用多重条件选择语句,充分体现面向对象设计思想
- 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
- 每个策略类使用一个策略类,符合单一职责原则
- 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
- 客户端不需要知道都有哪些策略类,符合最小知识原则
缺点
- 策略模式,当策略算法太多时,会造成很多的策略类
- 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过本文中的方式解决,也可以考虑使用IOC容器和依赖注入的方式来解决