Java电商活动商品推荐服务设计模式实践

531 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 

前言

  • 设计模式是软件众多软件开发人员经过相当长的一段时间的试验和错误总结的结果。面对频繁变化的业务需求,应用合适的设计模式,可以使我们的服务更加健壮,易修改,可维护。在团队合作中,能有效提升多人协作的效率,降低维护成本。
  • 常见的设计模式主要分为三类,分别为创建型模式、结构型模式和行为型模式。本文主要介绍创建型模式-工厂模式,行为型设计模式-策略模式实践经验。

业务场景介绍

我们公司是电商交易平台。在首页有活动商品的推荐功能。如下图。

image.png

用户可以点击切换不同的活动类型,每种活动展示的商品不同。 为了获取不同活动的展示商品。我们最开始使用以下伪代码。

 if (Objects.equals("周末清仓", type)) {
   // 周末清仓计算逻辑
   // return 商品列表
 } else if (Objects.equals("限时秒杀", type)) {
   // 限时秒杀计算逻辑
   // return 商品列表
 } else if (Objects.equals("百亿补贴", type)) {
   // 百亿补贴计算逻辑
   // return 商品列表
 } else if (Objects.equals("每日上新", type)) {
   // 每日上新计算逻辑
   // return 商品列表
 } else {
   //  ...
 }
  • 上面这种写法的好处是逻辑清晰,易于理解,实现起来也比较简单。缺点是计算逻辑与程序其他逻辑相耦合,在实际业务场景中,活动类型变化频繁,每次新增或者修改活动计算逻辑,都需要对这段代码修改,此时,代码也越来越长,增加了代码的维护成本。对于这个业务场景,我们一般可以采用策略设计模式来优化。

  • 什么是策略模式呢?策略模式是行为型模式的一种,简单来说,就是一个类的行为或其算法可以在运行时更改。在活动这个业务场景中,我们首先定义活动接口,然后将不同类型的活动相关逻辑,封装到具有共同接口又互相独立的类中,如下图。

image.png

  • 通过使用策略模式,程序就具有了良好的可拓展性,易修改,当某个策略修改的时候,只需要修改当前类即可,降低了维护成本。下面我们来具体实践应用一下,只涉及到主要流程,具体业务逻辑没有实现。

工程实践

基本代码

  • 活动策略接口
// 商品类
public class Good {
    private Integer id;

    private String name;  
}

public interface ActivityHandler {
    /**
    * 计算商品
    * @return 商品列表
    */
  
    public List<Good> calculate();
}
  • 活动策略实现类
// 周末清仓
public class WeekendClearanceImpl implements ActivityHandler{
    @Override
    public List<Good> calculate() {
        List<Good> goods = new ArrayList<>();
        // 具体计算逻辑

        return goods;
    }
}


// 限时秒杀
public class LimitTimeSellImpl implements ActivityHandler{
    @Override
    public List<Good> calculate() {
        List<Good> goods = new ArrayList<>();
        // 具体计算逻辑

        return goods;
    }
}
  • 客户端调用
// 根据参数调用对应的算法计算商品列表
public List<Good> acqurireGoods(String type) {
    List<Good> goods = new ArrayList<>();
    if (Objects.equals("周末清仓", type)) {
      // 周末清仓计算逻辑
      ActivityHandler activityHandler = new WeekendClearanceImpl();
      goods = activityHandler.calculate();
    } else if (Objects.equals("限时秒杀", type)) {
      // 限时秒杀计算逻辑
      ActivityHandler activityHandler = new LimitTimeSellImpl();
      goods = activityHandler.calculate();
    } else if (Objects.equals("百亿补贴", type)) {
      // 百亿补贴计算逻辑
      ActivityHandler activityHandler = new SubsidyImpl();
      goods = activityHandler.calculate();
    } else if (Objects.equals("每日上新", type)) {
      // 每日上新计算逻辑
      ActivityHandler activityHandler = new DailyNewImpl();
      goods = activityHandler.calculate();
    } else {
      //  ...
    }

    return goods;
}

上面就是我们最常见的实现代码。可以看出,商品计算逻辑已经实现了封装,可拓展,易修改。美中不足的是,在客户端调用的时候,依然有很多的if else语句, 不优雅。接下来,我们优化一下调用端的代码。

工厂模式优化客户端调用代码

  • 创建对象的优化,我们一般使用工厂模式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
  • 整体优化思路是我们定义活动枚举类,结合工厂设计模式,来实现创建对象解耦。具体实现核心代码如下:
  • 活动枚举类
public enum ActivityTypeEnum {
    WEEKEND_CLEARANCE("WeekendClearance","周末清仓"),
    LIMIT_TIME_SELL("LimitTimeSell","限时秒杀"),
    SUBSIDY("Subsidy","百亿补贴"),
    DAILY_NEW("DailyNew","每日上新");
    
    @Getter
    private String type;
    @Getter
    private String desc;
    ActivityTypeEnum(String type, String desc) {
        this.type = type;
        this.desc = desc;
    }
}
  • 活动对象工厂类
public class ActivityHandlerFactory {
    
    private static final Map<String, ActivityHandler> STRATEGY_MAP = ImmutableMap.<String, ActivityHandler>builder()
        .put(ActivityTypeEnum.WEEKEND_CLEARANCE.getType(), new WeekendClearanceImpl())
        .put(ActivityTypeEnum.LIMIT_TIME_SELL.getType(), new LimitTimeSellImpl())
        .put(ActivityTypeEnum.SUBSIDY.getType(), new SubsidyImpl())
        .put(ActivityTypeEnum.DAILY_NEW.getType(), new DailyNewImpl())
        ;

    /**
     * 根据类型获取对应的获取实现类
     *
     * @param type 类型
     * @return 类型对应的实现类
     */
    public ActivityHandler getStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }

}

  • 客户端调用
@Resource
ActivityHandlerFactory activityHandlerFactory;

public List<Good> acqurireGoods(String type) {

    ActivityHandlerFactory activityHandler = activityHandlerFactory.getStrategy(type);

    return Optional.ofNullable(activityHandler)
        .map(activityHandler -> activityHandler.calculate())
        .orElse(new ArrayList<>());
}
  • 通过工厂模式的优化,我们在接入新的策略的时候,在客户端调用的地方,只需要增加新的枚举类和优化工厂类的Map即可。
  • 在实际的业务开发中,我们使用比较多的框架是Spring, Spring 可以帮助我们创建和管理对象。上述工厂模式的代码,还需要自己创建对象,我们可以直接借助Spring API来优化创建对象。

Spring创建对象优化

  • Spring 是我们Java Web相关研发的事实标准,应用广泛。优化的思想是使用Spring框架中的InitializingBean接口与ApplicationContextAware接口来实现策略bean的自动装配。具体优化实现核心代码如下:
  • 活动接口
public interface ActivityHandler {
    /**
    * 计算商品
    * @return 商品列表
    */
    public List<Good> calculate();


    /**
    * 接口的实现类枚举类型
    * @return 接口的实现类枚举类型
    */
    String getType();
}
  • 活动对象工厂类
public class ActivityHandlerFactory implements InitializingBean, ApplicationContextAware {
    
    private static final Map<String, ActivityHandler> STRATEGY_MAP;

    private ApplicationContext applicationContext;


    /**
     * 根据类型获取对应的获取实现类
     *
     * @param type 类型
     * @return 类型对应的实现类
     */
    public ActivityHandler getStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }

    @Override
    public void afterPropertiesSet() {
        Map<String, ActivityHandler> beansOfType = applicationContext.getBeansOfType(ActivityHandler.class);

        STRATEGY_MAP = Optional.ofNullable(beansOfType)
                            .map(beansOfTypeMap -> beansOfTypeMap.values().stream()
                            .filter(activityHandler -> StringUtils.isNotEmpty(activityHandler.getType()))
                            .collect(Collectors.toMap(ActivityHandler::getType, Function.identity())))
                            .orElse(new HashMap<>(8));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

  • applicationContext是Spring的容器,InitializingBean接口中的afterPropertiesSet()方法在类的实例化过程当中执行,我们可以通过重写afterPropertiesSet()方法,在其中利用getBeansOfType()方法来获取到策略接口的所有实现类,并存于Map容器之中。

总结

  • 今天的文章主要分享了创建型模式-工厂模式,行为型设计模式-策略模式在我们项目中的实际应用。使用合适的设计模式,可以使我们的项目代码结构更加清晰,增加系统的可维护性。“纸上得来终觉,绝知此事要躬行。”上述文章优化实践,你也可以在项目中应用一下,在实践中更好的掌握设计模式的思想和应用。