设计模式之策略模式(如何优雅的去除if-else逻辑代码)

                          设计模式之策略模式(如何优雅的去除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 map=new HashMap(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 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 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 matchServcieList;

	public void match(String channelId,Map map) {
		 Optional 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 map) {
		try {
			doSomthing();
		} catch (Exception e) {
			logger.error("点击数据匹配失败:" + e.getMessage(), e);
		}

	}

	@Override
	public String getChannelId() {

		return CHANNELBAIDU;
	}

}

策略模式的优缺点

优点

  • 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
  • 避免使用多重条件选择语句,充分体现面向对象设计思想
  • 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
  • 每个策略类使用一个策略类,符合单一职责原则
  • 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
  • 客户端不需要知道都有哪些策略类,符合最小知识原则

缺点

  • 策略模式,当策略算法太多时,会造成很多的策略类
  • 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过本文中的方式解决,也可以考虑使用IOC容器和依赖注入的方式来解决

你可能感兴趣的:(设计模式)