JAVA孔乙己,工厂+策略模式的五种实现方式及优劣比较

工厂模式作为创建性设计模式中最为常用的一种,经常与策略模式结合使用,达到通过不同的条件,执行不同策略的目的。工作中经常碰到的一个场景就是,在消息下发的时候,不同的消息类型需要用不同的处理逻辑来进行应对。

工厂模式的核心,在于通过什么样的方式生产产品,也就是创建类返回给调用方。孔乙己会茴香豆的四种写法,而区区在下比他更胜一筹,懂得五种工厂模式的实现方式!这就为看官一一道来。

需求

针对一家商铺,有这样一个需求,对用户客户分为了普通客户、vip客户、超级vip用户、专属vip用户4个等级,每当用户购买商品时,针对不同的用户等级和消费金额采取不同的打折优惠策略。在平常的开发当中,必然会出现多层的if-else嵌套判断,先判断用户的等级再判断用户购买商品的消费金额。这里的代码就不展示了,非常的简单,并且不太优雅。

弊端

以上的情况出现了多层的if-else嵌套,除此之外,以后如果需求再有变动,需要再增加一个用户等级,那么又会再次添加if-else的嵌套判断,那么如何解决上述的弊端呢,采用策略模式和工厂模式的搭配使用,可以很好地优化多层if-else的多层嵌套

实现

(1)定义枚举类,用于决定执行什么策略

/**
 * 用户类型枚举类
 */
public enum UserPayServiceEnum {
    VIP(1,"Vip"),

    SUPERVIP(2,"SuperVip"),

    PARTICULALYVIP(3,"ParticularlyVip"),

    NORMAL(4,"NormalPayService");
    /**
     * 状态值
     */
    private int code;

    /**
     * 类型描述
     */
    private String value;
}

(2)定义具体策略

以下需要注意的是每个具体的策略类都实现了InitializingBean接口,它的作用是每当策略类被spring容器启动初始化后会调用afterPropertiesSet方法,而在这个方法里面的作用是会往工厂里针对不同用户等级保存其对应的用户策略引用

public interface UserPayService {
    /**
     * 计算应付价格
     */
    public BigDecimal quote(BigDecimal orderPrice);
}

//实现InitializingBean接口,容器启动后会调用afterPropertiesSet()方法,往工厂里写入打折策略
public class NormalPayService implements UserPayService, InitializingBean {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        return new BigDecimal("10");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        UserPayServiceStrategyFactory.register(UserPayServiceEnum.NORMAL.getValue(), this);
    }

}

//实现InitializingBean接口,容器启动后会调用afterPropertiesSet()方法,往工厂里写入打折策略
@Service
public class VipPayService implements UserPayService, InitializingBean {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        if (orderPrice.compareTo(new BigDecimal("100")) > 1) {
            return new BigDecimal("8");
        }
        return new BigDecimal("9");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        UserPayServiceStrategyFactory.register(UserPayServiceEnum.VIP.getValue(), this);
    }

}

(3)编写工厂类

这个工厂类中的核心,是静态方法register,每个策略类在Spring容器初始化之后,都会调用工厂类的静态注册方法,把自己的引用注册到map

//@Service
public class UserPayServiceStrategyFactory {

    private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();

    public  static UserPayService getByUserType(String type){
        return services.get(type);
    }


    public static void register(String userType,UserPayService userPayService){
        Assert.notNull(userType,"userType can't be null");
        services.put(userType,userPayService);
    }
}

扩展

工厂模式+策略模式的基本使用大致如上。但在map如何存储策略类的引用,又引申出了不同的方式。下面简单介绍一下。

(1):手动初始化对象

最为简单,在初始化工厂类的时候,把每个策略类都初始化一遍填充到map

public class ProcessHandlerFactory {

    private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();

    public  static UserPayService getByUserType(String type){
        return services.get(type);
    }

    static{
        services.put(UserPayServiceEnum.VIP.getValue(), new VipPayService());
        services.put(UserPayServiceEnum.SUPERVIP.getValue(), new SuperVipPayService());
        services.put(UserPayServiceEnum.PARTICULALYVIP.getValue(), new ParticularlyVipPayService());
        services.put(UserPayServiceEnum.NORMAL.getValue(), new NormalPayService());
    }
}

(2):反射每次调用获取策略实例

map中存放的value存放类而非具体的引用,在客户端每次调用的时候再通过反射去生成具体的类的实例,性能较差。

public class UserPayServiceStrategyFactory3 {

    private static Map<String, Class<? extends UserPayService>> services = new ConcurrentHashMap<>();

    //初始化map,存放策略
    static {
        services.put(UserPayServiceEnum.VIP.getValue(), VipPayService.class);
        services.put(UserPayServiceEnum.SUPERVIP.getValue(), SuperVipPayService.class);
        services.put(UserPayServiceEnum.PARTICULALYVIP.getValue(), ParticularlyVipPayService.class);
        services.put(UserPayServiceEnum.NORMAL.getValue(), NormalPayService.class);
    }

    //获取策略
    public static UserPayService getByUserType(String type) {
        try {
            Class<? extends UserPayService> userPayServiceClass = services.get(type);
            return userPayServiceClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return new NormalPayService();
        }

    }

}

(3):通过ApplicationContext获取

  1. 定义SpringUtil工具类,重写setApplicationContext方法获取Spring上下文
  2. 调用静态方法,传入类名获取实例
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Value("${spring.profiles.active}")
    private String activeProfileEnum;

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

   /**
     * 获取上下文
     *
     * @return
     * @author 2019-08-16 created by pengqiang.ai
     */
    private static ApplicationContext getContext() {
        return context;
    }

  	// 通过类名获取具体的类
    public static <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return getContext().getBean(name, requiredType);
    }
}

工厂类中,通过调用SpringUtil的静态方法,直接获取已经初始化过的bean填充到map

public class ProcessHandlerFactory {

    private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();

    public  static UserPayService getByUserType(String type){
        return services.get(type);
    }

    static{
        services.put(UserPayServiceEnum.VIP.getValue(), SpringUtil.getBean(UserPayServiceEnum.VIP.getValue(),VipPayService.class));
        services.put(UserPayServiceEnum.SUPERVIP.getValue(), SpringUtil.getBean(UserPayServiceEnum.SUPERVIP.getValue(),SUPERVIPPayService.class));

    }
}

(4):通过注解注册

上面的几种方式,虽然避免了if-else的代码冗杂,但是实际上,,不管哪一种方式修改的时候依然需要修改工厂类代码,不能够严格满足开闭原则,而通过注解注册的方式解决了这个问题。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyStrategy {
     /**
	* VIP类型
	*
	* @return
	*/
	UserPayServiceEnum getPayService();
}

@CompensationStrategy
public class NormalPayService implements UserPayService {
    @Override
    public BigDecimal quote(BigDecimal orderPrice) {
        return new BigDecimal("10");
    }
}

// 工厂类实现
public class Factory implements ApplicationListener<ContextRefreshedEvent> {

    private static final Map<String,UserPayService> map = new HashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        // 获取所有用自定义注解修饰的策略类
        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(MyStrategy.class);
        if (beans == null || beans.isEmpty()){
            return;
        }

        Iterator<Map.Entry<String, Object>> iterator = beans.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Object> next = iterator.next();
            Object bean = next.getValue();
            // 获取这个策略类里注解的属性,用于判断填充到哪个map里  
            MyStrategy annotation = AnnotationUtils.findAnnotation(AopUtils.getTargetClass(bean), MyStrategy.class);
            String type = annotation.type;
            // 再做一次校验,确保注解是注释在正确的地方
            if (bean instanceof UserPayService){
                map.put(type,(Service) bean);
            }
        }
    }
}

总结

性能排序:反射 < 手动初始化 < 通过注解 < spring上下文 < 策略类中注册

通常单纯的策略模式需要包含三个内容

  • 环境类(Context):保存策略引用
  • 抽象策略类:定义策略功能接口
  • 具体策略类(多个):继承抽象策略类,实现不同的策略

而工厂模式+策略模式的这种方式,实际上工厂类是代替了环境类的作用,属于一种变种写法。而在后面列举的几种写法中,推荐注解方式实现,性能虽然不是最优但也不差,最重要的是新增策略不需要修改map中的代码,只要在新策略类用自定义注解修饰,就能实现无感扩展!

#你觉得今年春招回暖了吗##JAVA##阿里#
全部评论
感谢大佬分享
点赞 回复 分享
发布于 2023-03-13 18:57 湖北
感谢分享,学习一下
点赞 回复 分享
发布于 2023-03-13 18:51 黑龙江

相关推荐

03-25 19:00
东北大学 Java
程序员牛肉:太好了,是聊天记录。不得不信了。 当个乐子看就好,不要散播焦虑
点赞 评论 收藏
分享
评论
5
1
分享

创作者周榜

更多
牛客网
牛客企业服务