6

策略模式在实际业务中的应用

 2 years ago
source link: https://segmentfault.com/a/1190000040897227
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

策略模式结构图

策略模式主要由以上三个身份组成,这里我们就不过多及时策略模式的基础知识,默认大家已经对策略模式已经有了一个基础的认识。

现有一个广告点击数据埋点上报的需求,上报的埋点数据根据点击的广告位置不同做区分进行上报,每个广告位置的数据进行分表存储。(eg:这里大家也不必深究分表存储为什么要这么做,我们只聊策略模式的实际应用)

由于是实战案例,那么我们是基于SpringBoot框架的,主要要使用的Spring的一些功能,所以大家要注意。

第一步:定义策略类

首先我们定义一个上报的接口

public interface AdvertisingDataReported {

    String advertisingDataReported(Object param);
}

第二步:定义具体的策略实现类

@Service
public class BottomAdvertisingDataReported implements AdvertisingDataReported {
    
    @Override
    public String advertisingDataReported(Object param) {
          // 具体的业务逻辑略
        return null;
    }
}

第三步:策略控制类

由于策略模式有好多具体的具体策略实现,那么到底使用哪一个策略需要根据我们的入参,也就是我们业务中的广告类型进行判断,那么我们该如何优雅的进行判断呢?

我们先看看这种方式

public static void main(String[] args) {
        
  String advertisingType = "1";

  if (advertisingType.equals("1")) {
    // 执行策略A
  } else if (advertisingType.equals("2")) {
    // 执行策略2
  }
}

这么写的大有人在,我们这里也不讨论这些问题。我们先看一下这么写存在哪些问题?

存在的问题:

1. 违反开闭原则,每次增加新的策略实现类,都要加一个if判断;
 2. 随着策略实现类的增加,代码变的臃肿,越来越难以维护;



基于这种情况,我们可不可以在项目启动的时候,将所有的策略实现类进行初始化,存储在Map当中,广告类型作为key,实现类作为Value,我们看如下代码:

@Component
public class StrategyFactory implements ApplicationContextAware {

    private final Map<String, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          // 返回该接口所有的实现类
        Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class);
        tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.getClass().getName(), strategyService));
    }

    public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(Class<T> clazz) {
        return STRATEGY_MAP.get(clazz.getName());
    }
}

我们的策略控制类实现了ApplicationContextAware,这个类你可以这么理解,它可以获得ApplicationContext的上下文,由于我们是至于SpringBoot讲这个案例的,我们的策略类实现类都加了@Service注解注入到了Spring容器中,所以我们可以直接从容器中,取到策略类的所有实现类。

获取到所有的策略实现类之后,我们把类路径作为key,类的实现作为value存储到了map中,到此我当时觉得就大功告成了。

大家觉得还存在什么问题?

我们怎么知道这个入参需要走哪个具体的策略类呢?还需要定义一个单独的类,来对广告类型和策略类进行映射,那这跟判断不又是同一个逻辑的吗?还得一直维护这个映射关系。

如果不想单独的定义一个类对广告类型和策略类进行一一映射,那么我们可不可以在策略类中进行解决,每个策略类实现类知道它要处理哪种类型,这样我们就可以把map中Key类路径的值替换为广告类型,这样就可以根据上报接口入参的广告类型,直接从Map中进行get。

具体的实现有两种,你可以自定义注解,通过加注解的方式进行区分,也可以使用方法,那么我们这里直接使用方法进行处理。

改造后的代码:

public interface AdvertisingDataReported {

      // 新增方法
    AdvertisingTypeEnum advertisingType();

    String advertisingDataReported(Object param);
}

策略实现类:

@Service
public class BottomAdvertisingDataReported implements AdvertisingDataReported {

    @Override
    public AdvertisingTypeEnum advertisingType() {
        return AdvertisingTypeEnum.BOTTOM;
    }

    @Override
    public String advertisingDataReported(Object param) {
        return null;
    }
}

策略控制类:

@Component
public class StrategyFactory implements ApplicationContextAware {

      // Map的Key改为广告类型枚举类
    private final Map<AdvertisingTypeEnum, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class);
        tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.advertisingType(), strategyService));
    }

      // 根据广告类型获取相应的策略类
    public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(AdvertisingTypeEnum advertisingTypeEnum) {
        return STRATEGY_MAP.get(advertisingTypeEnum);
    }
}

广告枚举类:

public enum AdvertisingTypeEnum {

    BOTTOM, TOP;

    private String advertisingType;

    AdvertisingTypeEnum() {}
  
      // set get省略
}

策略类的具体使用

@RestController
public class AdvertisingDataReportedController {

    @Resource
    private StrategyFactory strategyFactory;

    @RequestMapping(value = "/reported/data", method = RequestMethod.POST)
    public String reportedData(AdvertisingTypeEnum advertisingTypeEnum, Object obj) {

        AdvertisingDataReported dataReported = strategyFactory.getInstance(advertisingTypeEnum);

        String result = dataReported.advertisingDataReported(obj);

        return "SUCCESS";
    }
}

小小总结:

到这里我们这个策略模式的案例就算结束了,有几个问题不知道大家有没有疑惑,为什么我要用Object作为方法的入参,我们这种案例中,好像每个策略类的入参好像都是一样的,但是也有可能出现同一个策略的实现类,但是入参完全可能不相同,那么这个时候,我们就可以通过传入Object的方式,在方法内部进行转换,当然了,如果这样你嫌策略方法太死板了,那么你也可以在方法上加入泛型,具体转换为什么类型,通过调用者传入泛型来转换。

经过这样一番改造之后,刚才我们遇到的两个问题也都统统不是问题了,我们想要新增一个策略实现类,只需要实现定义的策略类即可,无需增加额外的任何代码。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK