RefineCode-策略先行

Scroll Down

背景

业务开发中避免不了使用if/else,但是业务越来越大的时候,需要的约束条件呢又越来越多,就会导致这个代码出现很多的分支判断,最后的情况可以能就是下图这个样.

这里写图片描述

这显然那个不是我们想要的最终结果,但是开发着开发着就就变成这样了,于是就着手去修改试着干掉if/else

1、选用策略模式为基础,提供模版方法。

2、高度定制,借鉴策略的思想

    public void Buy(double originPriceM)
        {
            if (totalAmount >= 5000 * 100)
            {
                vip = Vip.钻石会员;
                vipAlgorithm = new VipDiamond();
            }
            else if (totalAmount >= 3000 * 100)
            {
                vip = Vip.黄金会员;
                vipAlgorithm = new VipGold();
            }
            else if (totalAmount >= 1000 * 100)
            {
                vip = Vip.白银会员;
                vipAlgorithm = new VipOrdinary();
            }
            else
            {
                vip = Vip.注册会员;
                vipAlgorithm = new VipNone();
            }
            double originPrice = (int)originPriceM * 100;
            double finalPrice = vipAlgorithm.CalcPrice(originPrice);
            Console.WriteLine($"您在本店历史消费总额:{totalAmount * 0.01}元");
            String vipMsg = "您是本店会员:{vip.Value.ToString()}";
            Console.WriteLine(vipMsg);
            Console.WriteLine($"本次购买商品原价{originPrice * 0.01}元,需支付{finalPrice * 0.01}元");
            totalAmount += originPrice;
        }
    }

上面的代码描述了用户购买商品,等级升级,金额优惠的行为,假如当VIP的设定增加的就会多增加一套分支判断,这无疑是很操蛋。如果做大做强还得优化此处的代码。

策略模式优化

在策略模式中,一个类的行为或其算法可以在运行时更改,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的上下文对象。上下文根据不同的策略执行不同的算法。

这里写图片描述

主要解决:

在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

应用场景:

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

  • 设定VIP策略
public interface VIP {
   Map doOperation(double originPriceM);
}
  • 黄金VIP策略
public class 黄金 implements VIP {
   Map doOperation(double originPriceM){
   	return new HashMap《》
   }
}
  • 上下文对象
public class Context {
   private VIP vip;
 
   public Context(VIP vip){
      this.vip = vip;
   }
 
   public Map execute(double originPriceM){
      return vip.doOperation(originPriceM);
   }
}

在计算会员的折扣的这个场景,使用策略模式来优化,调用的时候直接通过对应的vip等级来构建上下文对象,然后把在需要获取会员优惠时候,调用execute方法,可以起到啥作用呢,貌似也就是省了几个if/else,,但是当店铺做大做强呢,会员的等级飙升的时候,你这个策略的实现类无疑会飙升,会膨胀,这就是弊端啊,当策略过多,就得使用混合模式来处理,一般是配合工厂模式,或者是模板方法,来降低策略实现类的膨胀。主要的使用方式:在工厂中依据业务的key来做前缀,拼接上模板类就是其策略实现类,然后创建对应的实现类的对象,然后执行模板方法即可。这样实现子类可以内部定义 任意方法,只要执行在模板方法中调用自定义的即可。这么搞软件的复杂度高了,有点过度设计的味道了,慢慢的发现还不如if/else呢。

高度定制,借鉴策略的思想

就算混合模式也不是我想要的最优解,那样会很也是避免不了在业务维度的多种策略的场景,怎么说呢如果使用策略模式,那么以商品来看,我会员优惠力度可以是一个策略,那么信用分决定的退款时间也是一个策略,这样就策略实现类随着需求的增加而增加,也不是很好,而且声明了多个策略接口,搞的后来接受的兄弟们反手就继续if/else开发。那么如何考虑解决策略优化方式的弊端呢,我们使用自定义注解来声明条件处理器,和条件处理策略。注解标识类代表该类是策略处理实现类,注解标识方法代表是满足该注解condition值的分支路线,支持分支路线是否返回结果。同时也需要上下文容器来维系所有的处理器。所有的处理器通过ClassScan被囊括进来。在执行的分支决断的时候需要从上下文对象中获取bean对象,绑定之后去反射执行。

  • 指定注解处理器
/**
 * @author: by Mood
 * @date: 2019-12-09
 * @Description: 处理器 应用在类上声明该类为分支判断处理器与Spring集成,name就是BeanName,应用在方法上标示该方法未满足注解中condition值的处理
 * @version: 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HandlerCondition {

   /**
     * 标示处理器
     * @return
     */
    String name()default "";
    /**
     * 编码化的判断条件
     * @return
     */
    String condition()default "";

}
  • 指定上下文容器
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClassMethod {
    private Class clazz;
    private Method method;
}
@Slf4j
public class HandlerContext {

    private Map<String, Map<String, ClassMethod>> conditionHandlerMap;
    public HandlerContext(Map<String, Map<String, ClassMethod>> conditionHandlerMap){
        this.conditionHandlerMap=conditionHandlerMap;
    }
    public Object getInstance(String config) throws Throwable {
        return Optional.ofNullable(config).map(SpringUtil::getBean).orElseThrow(()->new Exception(“conditionHandler class not found”));
    }
    public Object invokeHandler(Object handler,String type,String config) throws Throwable {
        return Optional.ofNullable(conditionHandlerMap.get(config))
                .map(x->x.get(type)).map(ClassMethod::getMethod).map(x->{
            try {
                return x.invoke(handler);
            } catch (IllegalAccessException e) {
               log.error("invoke method matching error");
            } catch (InvocationTargetException e) {
                log.error("invoke method matching error");
            }
            return null;
        }).orElseThrow(()->new Exception("condition match error !"));
    }
}
  • 指定包扫描器
@Slf4j
public class ClassScaner implements ResourceLoaderAware {
    //保存过滤规则包含的注解
    private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
    public static Set<Class> scan(String[] basePackages,
                                  Class<? extends Annotation>... annotations) {
        ClassScaner cs = new ClassScaner();
        Optional.ofNullable(annotations).ifPresent(x->{
            Arrays.stream(x).forEach(c->cs.addIncludeFilter(new AnnotationTypeFilter(c)));
        });
        return Optional.ofNullable(basePackages).map(x-> Arrays.stream(x).map(c->cs.doScan(c)).collect(() -> new HashSet<Class>(),
                (set ,p) ->set.addAll(p),(m ,n) -> m.addAll(n))).orElseGet(HashSet::new);
    }

    public static Set<Class> scan(String basePackages, Class<? extends Annotation>... annotations) {
        return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils
                .getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(
                resourceLoader);
    }

    public void addIncludeFilter(TypeFilter includeFilter) {
        this.includeFilters.add(includeFilter);
    }
    private Set<Class> doScan(String basePackage) {
        Set<Class> classes = new HashSet<Class>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX  + org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/**/*.class";
            classes=Arrays.stream(this.resourcePatternResolver.getResources(packageSearchPath)).filter(Resource::isReadable).map(x->{
                try {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(x);
                    if ((includeFilters.size() == 0 ) || matches(metadataReader)) {
                        return Class.forName(metadataReader.getClassMetadata().getClassName());
                    }
                } catch (ClassNotFoundException | IOException e) {
                    log.error("ClassScaner Error is {}",e.getMessage(),e);
                }
                return null;
            }).filter(Objects::nonNull).collect(Collectors.toSet());
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return classes;
    }

    private boolean matches(MetadataReader metadataReader) {
        return this.includeFilters.stream().map(x-> {
            try {
                return x.match(metadataReader, this.metadataReaderFactory);
            } catch (IOException e) {
                log.error("message : {}",e.getMessage());
            }
            return false;
        }).findFirst().orElse(false);
    }

    public static void main(String[] args) {
        ClassScaner.scan("com.guazi.sale.carservice.app.tasks", Service.class)
                .forEach(clazz -> System.out.println(clazz));
    }
}
  • 填充上下文容器,借助BeanFactoryPostProcessor,当然你也可以使用@PostConstruct
@Component
public class HandlerProccess implements BeanFactoryPostProcessor {

    private static final String HANDLER_SCAN_BNASE_PACKAGE="com.xxx.xxx.handle";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, Map<String, ClassMethod>> configHandlerMap = Maps.newHashMap();
        ClassScaner.scan(HANDLER_SCAN_BNASE_PACKAGE, HandlerCondition.class).forEach(clazz -> {

            Optional.ofNullable(clazz).filter(x->x.isAnnotationPresent(HandlerCondition.class)).ifPresent(y->{
                HandlerMark classHandlerCondition =(HandlerMark)y.getAnnotation(HandlerCondition.class);
                Map<String, ClassMethod> handlerMap = Maps.newHashMap();
                Arrays.stream(y.getMethods())
                        .filter(method -> method.isAnnotationPresent(HandlerCondition.class))
                        .forEach(m->{
                            HandlerMark methodHandlerCondition =m.getAnnotation(HandlerCondition.class);
                            handlerMap.put(methodHandlerCondition.vlaue(), new ClassMethod(clazz,m));
                        });
                configHandlerMap.put(classHandlerCondition.type(), handlerMap);
            }); });
        HandlerContext context = new HandlerContext(configHandlerMap);
        beanFactory.registerSingleton(HandlerContext.class.getName(), context);
    }
}

  • 示例处理器(垃圾分类)
@Component
@HandlerMark(type = "garbageHandel")
public class GarbageHandel  {
    /**
     * 投放厨房垃圾
     *
     * @return
     */
    @HandlerMark(vlaue = "kitchenGarbage")
    public String kitchenGarbage() {
        return "绿色垃圾桶";
    }

    /**
     * 投放有害垃圾
     *
     * @return
     */
    @HandlerMark(vlaue = "hazardousGarbage")
    public String hazardousGarbage() {
        return "灰色垃圾桶";
    }
}


  • 示例处理器(直播平台分级)
@Component
@HandlerMark(type = "liveLevelHandel")
public class LiveLevelHandel  {
    /**
     * 斗鱼直播
     *
     * @return
     */
    @HandlerMark(vlaue = "douyu")
    public String douyu() {
        return "海鲜台";
    }

    /**
     * 虎牙直播
     *
     * @return
     */
    @HandlerMark(vlaue = "huya")
    public String huaya() {
        return "直播个锤子";
    }
}

  • 代码演示
    @Autowired
    HandlerContext handlerContext;
    
     public void testDemo(String garbageType) throws Throwable {
        System.out.println(handlerContext.invokeHandler( handlerContext.getInstance("garbageHandel"),garbageType"garbageHandel"));
    }
     public void testDemo(String liveType) throws Throwable {
        System.out.println(handlerContext.invokeHandler( handlerContext.getInstance("liveLevelHandel"),liveType"liveLevelHandel"));
    }


总结

使用方法1可以满足当策略比较少的情况,方法二代码难度有一点,但是每个处理器都是一个维度,每个处理器内部的方法就相当于一个动态的分支路线。可以再让注解condition值支持表达式,这样就更加的宽泛的匹配不同的分支处理。美滋滋。重构之后项目也变得清秀了。