策略模式实践方案和优化

一、布景

在业务开发中,咱们难免遇到如下的操控逻辑,有的分支逻辑大,有的小,而在一个项目中,咱们通常会考虑把比较核心的多分支逻辑用战略形式进行完结,然后完结杰出的可扩展性。

策略模式实践方案和优化

在该文章中,咱们试图经过各段代码案例来调查战略形式的多种完结办法,并比照剖析其优缺点。

为此,咱们先准备一些基础的代码片段

// 优惠券笼统类 - 处理器
public abstract class CouponHandler {
	// 经过该办法在子类中完结了与枚举类的相关联系
	public abstract CouponTypeEnum getType();
	public abstract String deal();
}
/**
 * 满减类型优惠券 - 处理器
 */
@Service
public class CashBackHandler extends CouponHandler {
	@Override
	public CouponTypeEnum getType() {
		return CouponTypeEnum.CASH_BACK;
	}
	@Override
	public String deal() {
		System.out.println("履行:满减类优惠券处理逻辑");
		return "履行成果(满减类)";
	}
}
/**
 * 扣头类型优惠券 - 处理器
 */
@Service
public class PercentOffHandler extends CouponHandler {
	@Override
	public CouponTypeEnum getType() {
		return CouponTypeEnum.PERCENT_OFF;
	}
	@Override
	public String deal() {
		System.out.println("履行:扣头类优惠券处理逻辑");
		return "履行成果(扣头类)";
	}
}
/**
 * 优惠券类型枚举
 */
@Getter
@AllArgsConstructor
public enum CouponTypeEnum {
	CASH_BACK("满减"),
	PERCENT_OFF("扣头"),
	;
	private String desc;
}

二、计划

(一)、计划一(多完结)
@Component
public class CouponFactory implements InitializingBean, ApplicationContextAware {
    private static final Map<String, CouponHandler> MAP = new ConcurrentHashMap<>();
    private ApplicationContext appContext;
    @Override
    public void afterPropertiesSet() throws Exception {
        // 将 Spring 容器中所有的 CouponHandler 注册到 MAP
        appContext.getBeansOfType(CouponHandler.class)
                .values()
                .forEach(handler -> MAP.putIfAbsent(handler.getType().name(), handler));
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
    public String execute(CouponTypeEnum couponTypeEnum) {
        // 履行逻辑
        CouponHandler couponHandler = MAP.get(couponTypeEnum.name());
        if (couponHandler == null) {
            return null;
        }
        return couponHandler.deal();
    }
}
  1. CouponFactory完结InitializingBean, ApplicationContextAware,重写相应的办法;
  2. 运用@Component或许@Service或@Configuration润饰,将其交给spring容器管理;
  3. 运用map存储映射联系,为了避免线程安全问题,需选用ConcurrentHashMap(假设有修正map的操作的话也不至于出现线程安全问题);
  4. 重写setApplicationContext办法,该办法是ApplicationContextAware的接口办法,根据接口名称,能够知道该类是用于感知Spring容器上下文的,重写该办法,获取applicationContext上下文目标,便利咱们上面的CouponFactory类感知到applicationContext的存在,然后经过这个上下文目标进行一些别的操作,例如上面的getBeansOfType办法; 【注:Spring中所有承继Aware类的,都是为了让目标感知容器,因为在Spring中,容器能够很便利感知到目标,但有时分咱们需求在目标中感知容器】
  5. 重写afterPropertiesSet办法,该办法是InitializingBean的接口办法,用于在类初始化(参考类初始化进程)进程中,在特点设置完结之后进行的一些操作。也便是在类初始化进程的最后初始化阶段,完结了静态变量赋值进程之后,进行的一些切入操作;
  6. 其他类运用时,注入CouponFactory,调用其execute并传入枚举值即可。
(二)计划二(单完结)
@Component
public class CouponFactory implements ApplicationContextAware {
    private static final Map<String, CouponHandler> MAP = new ConcurrentHashMap<>();
    @Override
    public void setApplicationContext(ApplicationContext appContext) throws BeansException {
        appContext.getBeansOfType(CouponHandler.class)
            .values()
            .forEach(handler -> MAP.putIfAbsent(handler.getType().name(), handler));
    }
    public String execute(CouponTypeEnum couponTypeEnum) {
        // 履行逻辑
        CouponHandler couponHandler = MAP.get(couponTypeEnum.name());
        if (couponHandler == null) {
            return null;
        }
        return couponHandler.deal();
    }
}
  1. 在计划一的基础上,咱们削减了一个完结类;
  2. 不再把ApplicationContext作为CouponFactory的特点;
  3. 和计划一一样,咱们经过getBeansOfType从Spring容器中获取优惠券处理器笼统类CouponHandler的子类实例与枚举建立联系;
(三)计划三(无完结、注解)
@Component
public class CouponFactory {
    @Resource
    private Map<String, CouponHandler> map;
    private static final Map<String, CouponHandler> H_MAP = new ConcurrentHashMap<>();
    @PostConstruct
    public void init() {
        // map无需判空,假如CouponHandler没有完结类,发动就会报错,虽然为非受检反常,可是这个在研发进程中就需求编写笼统类的子类
        map.values().forEach(handler -> H_MAP.putIfAbsent(handler.getType().name(), handler));
    }
    public String execute(CouponTypeEnum couponTypeEnum) {
        // 履行逻辑
        CouponHandler couponHandler = H_MAP.get(couponTypeEnum.name());
        if (couponHandler == null) {
            return null;
        }
        return couponHandler.deal();
    }
}
  1. 在这个计划中,咱们不完结任何接口,也不承继任何笼统类
  2. 运用了@PostConstruct注解,根据该注解完结CouponFactory中优惠券处理器类的map构建
  • @PostConstruct注解归于java,与Spring结构进行别离
  • 该注解实践是在目标初始化完结后运用反射调用该init()办法来完结一定的逻辑
  • @PostConstruct注解的逻辑为什么不放在CouponFactory类的结构办法中?因为在结构办法对@Autowaired润饰的特点进行赋值并不是一个理想的操作,当@Autowaired润饰时,@Autowaired的特点会在结构办法之后才履行,会把编写的逻辑覆盖,而@PostConstruct会在@Autowaired之后才履行
(四)计划四(无完结、无注解)
/**
* 工厂类
*/
@Component
public class CouponFactory {
	@Resource
	private Map<String, CouponHandler> map;
	public String execute(CouponTypeEnum couponTypeEnum) {
		// 首字母转小写
		char[] chars = couponTypeEnum.getHandler().toCharArray();
		chars[0] += 32;
		// 获取处理器
		CouponHandler couponHandler = map.get(String.valueOf(chars));
		// 履行逻辑
		return couponHandler.deal();
	}
}

运用这个计划需求修正一下枚举类,而且能够将处理器的getType办法删去,如下

/**
* 优惠券类型枚举
*/
@Getter
@AllArgsConstructor
public enum CouponTypeEnum {
	CASH_BACK("满减", CashBackHandler.class.getSimpleName()),
	PERCENT_OFF("扣头", PercentOffHandler.class.getSimpleName()),
	;
	private String desc;
	private String handler;
}
/**
 * 优惠券笼统类
 */
public abstract class CouponHandler {
	// 【计划四的这个办法就能够删掉了,因为已经在枚举类里边做了相关】
	public abstract CouponTypeEnum getType();
	public abstract String deal();
}
  1. 运用@Resource或许@Autowaired注入map时,会主动获取Spring容器中的子完结类,并赋值,最后map其实是一个LinkHashMap,key为容器中的beanName,value为实例目标;
  2. 因为key的首字母小写,而枚举值得ClassName首字母为大写,例如key=cashBackService,enum中=CashBackService,因此需求转化一下;
  3. 为了进步转化效率,能够运用ascii码的规矩进行处理,而不选用字符串操作;
  4. 将计算逻辑放在这里(这里有坑,后边会提到)是因为依照枚举类的运用原则,不应在枚举中放入不应有的逻辑;
(五)计划五(无完结、无注解、削减运算)

计划四因为每次履行execute办法都需求计算一遍,为了削减这样的运算次数,能够考虑放入map中,既能够放入@Resource注入的map中,也能够放入另外的ConcurrentHashMap中。没错,看到这里,其实应该想到在这样的计算进程中,遇到并发,是会有问题的,怎样处理呢?请看计划六

@Component
public class CouponFactory {
    @Resource
    private Map<String, CouponHandler> map;
    public String execute(CouponTypeEnum couponTypeEnum) {
        CouponHandler couponHandler = map.get(couponTypeEnum.getHandler());
        if (couponHandler == null) {
            // 首字母转小写
            char[] chars = couponTypeEnum.getHandler().toCharArray();
            chars[0] += 32;
            // 获取处理器
            couponHandler = map.get(String.valueOf(chars));
            // 不为空则将首字母大写的key和对应的value放入map
            if (couponHandler != null) {
                map.putIfAbsent(couponTypeEnum.getHandler(), couponHandler);
            }
        }
        // Assert
        if (couponHandler == null) {
            return null;
        }
        // 履行逻辑
        return couponHandler.deal();
    }
}
@Component
public class CouponFactory {
    @Resource
    private Map<String, CouponHandler> map;
    private static final ConcurrentHashMap<String, CouponHandler> conMap = new ConcurrentHashMap<>();
    public String execute(CouponTypeEnum couponTypeEnum) {
        CouponHandler couponHandler = conMap.get(couponTypeEnum.getHandler());
        if (couponHandler == null) {
            // 首字母转小写
            char[] chars = couponTypeEnum.getHandler().toCharArray();
            chars[0] += 32;
            // 获取处理器
            couponHandler = map.get(String.valueOf(chars));
            // 不为空则将首字母大写的key和对应的value放入conMap
            if (couponHandler != null) {
                conMap.putIfAbsent(couponTypeEnum.getHandler(), couponHandler);
            }
        }
        // Assert
        if (couponHandler == null) {
            return null;
        }
        // 履行逻辑
        return couponHandler.deal();
    }
}
(六)计划六(无完结、无注解、简化)

在这个计划中,咱们需求改改优惠券的战略完结类,如下

    /**
    *  满减类型优惠券 - 处理器
    */
    @Service("CashBackHandler")
    public class CashBackHandler extends CouponHandler {
        @Override
        public String deal() {
            System.out.println("履行:满减类优惠券处理逻辑");
            return "履行成果(满减类)";
        }
    }
    /**
    *  扣头类型优惠券 - 处理器
    */
    @Service("PercentOffHandler")
    public class PercentOffHandler extends CouponHandler {
        @Override
        public String deal() {
            System.out.println("履行:扣头类优惠券处理逻辑");
            return "履行成果(扣头类)";
        }
    }
    /**
    *  工厂类
    */
    @Component
    public class CouponFactory {
        @Resource
        private Map<String, CouponHandler> map;
        public String execute(CouponTypeEnum couponTypeEnum) {
            // 删去首字母转小写的逻辑,因为前面@Service加上了value,没有写的情况下首字母会主动转小写,写了的话就会以这个首字母大写的name(id)作为标识
            // 获取处理器
            CouponHandler couponHandler = map.get(couponTypeEnum.getHandler());
            // 履行逻辑
            return couponHandler.deal();
        }
    }
  1. 比照计划五,咱们将计算逻辑全部删去;
  2. 既然计算逻辑是为了处理从注入的map中的key的首字母是小写的问题,那咱们就把key改改,让key也是大写就行了,这样就能够和枚举的值对应上了,因此咱们修正@Service的value,这样问题就得到处理了;
  3. 在这个计划中,咱们既不需求选用ConcurrentHashMap这样的有锁数据结构,也不需求担心execute办法的安全问题,毕竟map只在发动的时分写入,后边都是读操作,当然,关于deal()内部的逻辑的线程安全问题是需求考虑的;

总结一下: 1、计划一和计划二都需求完结接口 2、计划三不需求完结接口,而是选用@PostConstruct注解完结,与结构解耦 3、计划四将相关逻辑移到枚举中,同时增加了一丢丢运算,存在并发安全问题 4、计划五为了削减运算次数,增加了逻辑,不只代码增加了,也未处理并发安全问题 5、计划六在计划四和计划五的基础上,经过修正@Service的value来修正类在容器中的beanID,然后完结无运算下相关联系的建立 6、计划六可能存在因为修正了beanID,导致bean的覆盖问题

综上所述,能够选用计划三或许计划六来完结战略形式。 注:在这些完结计划中,也有用到工厂形式,经过Spring的bean工厂来完结了自己的工厂形式。

有人就问了,为什么不把 @Resource private Map\<String, CouponHandler> map; 放到外面,就不必CouponFactory这个类了?

从完结视点讲是能够的,可是咱们搞研发,要的便是一个可扩展,从这个视点,咱们假如搞个工厂来完结,不就变成了战略形式+工厂形式吗?这样在面试的时分不就能够多些内容能够吹? 正经点说的话,咱们应该这么考虑,当咱们运用一个工厂类来完结之后,后边咱们就能够很便利地转为笼统工厂形式,一个服务里边,或许不止一个当地会用到战略,那咱们就能够升级到笼统工厂形式。这样,扩展性有了,也不必增加许多工厂类,还使得代码具备了灵活性,最重要的是咱们多了一个能够用来面试吹水的知识点,何乐而不为?