策略模式与消除If-else

策略模式

策略模式是很常见的一种设计模式,将具体的算法封装起来,可以平等替换,避免客户端过多的涉及业务逻辑。网上有很多讲策略模式的文章,都很好。但也有一些文章强行将策略模式、状态模式和消除if-else联系起来,这是不对的。策略模式只是封装了各自算法逻辑,使其可以自由替换,如何选择仍然需要客户端来决定。

举个简单的会员充值的例子,初级会员充值100得100,中级会员充值100得150,高级会员充值100得200。

public interface IMember {
    /**充值*/
    public void charge(double money);
}

@Slf4j
public class PrimitiveMember implements IMember {
    @Override
    public void charge(double money) {
        log.info("初级会员充值不享受优惠,充值{}",money);
    }
}

@Slf4j
public class MiddleMember implements IMember{
    @Override
    public void charge(double money) {
        log.info("高级会员充值享受1.5倍,充值{}",money * 1.5);
    }
}

@Slf4j
public class HighMember implements IMember {
    @Override
    public void charge(double money) {
        log.info("高级会员充值享受2倍,充值{}",money * 2);
    }
}

我们将不同的充值策略进行了封装,通过接口开放出来。为了简单起见,策略的上下文类就省略了,直接在客户端类调用

public class Client {

    public static void main(String[] args) {
        IMember member = new HighMember();
        member.charge(100);
    }
}

在这里插入图片描述
这是一个比较经典的策略模式的例子,网上大部分的例子都是这样及其变种,号称解决了if-else问题。其实在客户端生成策略的时候,这里已经定死了是高级会员,高级会员如何得来的呢?肯定还是需要if-else作出选择的,例如:

public class Client {

    public static void main(String[] args) {
        IMember member = null;
        double store = 1000; // 储值
        double money = 100;

        if(store < 500){// 低于500,是初级会员
          member = new PrimitiveMember();
        }else if(store < 1000){// 500~1000中级会员
          member = new MiddleMember();
        }else if(store >= 1000){// 1000以上高级会员
            member = new HighMember();
        }

        member.charge(money);
    }
}

有的文章然后用工厂模式优化了一下策略获取:

public class MemberFactory {

    public IMember getMember(double store){
        IMember member = null;

        if(store < 500){
            member = new PrimitiveMember();
        }else if(store < 1000){
            member = new MiddleMember();
        }else if(store >= 1000){
            member = new HighMember();
        }

        return member;
    }
}

public class Client {

    public static void main(String[] args) {

        IMember member = null;

        double store = 1000;

        double money = 100;

        member = new MemberFactory().getMember(store); // 通过工厂类获取具体的策略

        member.charge(money);
    }
}

这样看起来客户端代码似乎消除了if-else,但实际上它并没有消失,只是转移到了工厂类里面。策略模式天生没有选择算法的功能,它只是封装了算法而已。因此if-else仍然需要想办法解决,这里我们可以通过注解来实现。

消除if-else

通过施加在策略类的上注解,我们可以很轻松的区别不同的策略,这样要采用怎样的策略类,就可以通过操作注解来实现。例如,我们加密算法有BASE64,MD5和RSA三种不同的加密策略。

/**
 * 业务策略接口
 * Created by gameloft9 on 2019/8/5.
 */
public interface ISigner{
    /**
     * 计算签名
     * */
    String doSign(String input);
}

@Slf4j
@StrategyComponent // 通过该注解标记该类是策略类
@SignType(type = SignTypeName.BASE64)
@Component
public class BASE64Signer implements ISigner {

    public String doSign(String input) {
        log.info("进行BASE64加密");
        return "";
    }
}

@Slf4j
@StrategyComponent
@SignType(type = SignTypeName.MD5)
@Component
public class MD5Signer implements ISigner {
    public String doSign(String input) {
        log.info("进行MD5加密");
        return "";
    }
}

@Slf4j
@StrategyComponent
@SignType(type = SignTypeName.RSA)
@Component
public class RSASigner implements ISigner {
    public String doSign(String input) {
        log.info("进行RSA加密");
        return "";
    }
}

我们通过SignType注解来区分不同的加密算法,SignType注解代码如下:

/**
 * 1、策略分类注解,根据它区分不同的策略实现类,他是实现if-else消除的关键
 * 2、如果策略不稳定,数目较多,可能变化。那么建议将策略的定义配置放入数据字典,apllo或者配置文件中
 *    然后注解仅提供一个key值,在工厂类的accept方法里读取数据字典,再执行选择逻辑。
 * Created by gameloft9 on 2019/8/5.
 */
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SignType {

    public SignTypeName type();
}

/**
 * 签名类型枚举
 * Created by gameloft9 on 2019/8/5.
 */
public enum SignTypeName {

    BASE64("BASE64"),
    MD5("MD5"),
    RSA("RSA");

    private String name;

    private SignTypeName(String name){
        this.name = name;
    }
}

注解的定义需要根据业务来定制,例如这里只需要提供签名类型名称即可。
@StrategyComponent注解表明该类是一个策略实现类,后面会扫描所有有该注解的类,并放入策略类的集合中。这样获取具体策略的时候,就扫描策略类集合,根据注解判断是否是符合条件的策略类,如果符合则返回该类的一个实例。策略的获取通过工厂类实现:

/**
 * 具体的业务策略工厂类
 * 1、添加@StrategyScan注解指定策略扫描包路径
 * 2、注入业务分类的策略注解class
 * 3、指定从何处获取策略类实例,spring容器或者反射实例化。默认反射实例化。
 * 3、实现具体策略的匹配逻辑
 * Created by gameloft9 on 2019/8/5.
 */
// 需要通过StrategyScan指定扫描策略包路径,包含子目录
@StrategyScan(value = "com.gameloft9.demo.sign")
@Component
public class SignerStrategyFactory extends StrategyFactory {

    @Override
    public Class<?> strategyAnnotationClass(){
       return SignType.class;// 注入一个策略分类注解
    }

    @Override
    public boolean getStrategyFromSpringContext(){
        return true; // 从spring context中获取策略实例bean
    }

    @Override
    public boolean accept(Annotation annotation,Object... params) {
        String type = params[0].toString();
        SignType signType = (SignType)annotation;

        return StringUtils.equals(signType.type().name(),type); // 匹配策略
    }
}

策略工厂类首先需要通过@StrategyScan(value = “com.gameloft9.demo.sign”)注解指定具体策略类的包位置,这样工厂类会自动扫描该包,并获取所有的策略类。
具体的策略注解是根据业务变化的,因此需要向工厂类提供注解的类型。
如果具体策略类是spring容器托管的,那么需要告知工厂类从spring容器中获取策略实例。
具体的选择逻辑通过accept方法实现,annotation是该策略类上的策略注解,params是传入参数。如果匹配则返回true,那么就会获取该策略类的实例。

下面我们来测试下:

/**
 * 测试代码,通过简单的反射获取实例
 * Created by gameloft9 on 2019/8/5.
 */
public class TestSiger {

    public static void main(String[] args) {

        String input = "GAMELOFT9";
        String type = "RSA";

        ISigner signer = (ISigner) new SignerStrategyFactory().createStrategy(type);

        signer.doSign(input);
    }
}

在这里插入图片描述
下面测试通过spring容器获取实例:

/**
 * 测试代码,通过spring容器获取策略实例
 * Created by gameloft9 on 2018/8/7.
 */
@Controller
@EnableAutoConfiguration
@Slf4j
@ComponentScan(basePackages="com.gameloft9.demo")
public class TestStrategy {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(TestStrategy.class, args);

        String input = "GAMELOFT9";
        String type = "RSA";

        ISigner signer = (ISigner) ContextUtil.getBean(SignerStrategyFactory.class).createStrategy(type);

        signer.doSign(input);
    }
}

在这里插入图片描述
这样我们就真正的消除了if-else,如果我们将策略类的定义存入配置文件、apollo配置中或者数据库中,我们还可以做到策略类的定义可配置化。例如:

    @Override
    public boolean accept(Annotation annotation,Object... params) {
        String type = params[0].toString();
        SignType signType = (SignType)annotation;
         
         // 这里添加配置的获取逻辑,从数据库、配置文件或者apollo中获取策略类的定义
         // 此时策略注解只需提供一个key即可。
         ......
         
        return StringUtils.equals(signType.type().name(),type); // 匹配策略
    }

后记

消除if-else,方法有不少,下面总结三种:
1-先判断不符合,然后直接返回,这样可以少嵌套一层if。例如:

if(条件满足){
  if(条件满足){
  }else{
  }
}

// 条件不满足提前返回
if(条件不满足){
return ;
}

if(条件满足){
}else{
}

2-如果if内部的逻辑比较复杂,可以抽取成方法出来。例如:

if(){
 if(){}
 if(){
    if(){
    }else{
    }
 }
}
// 转换成如下调用方式
if(){
  method()
}

// 将内部调用抽取成方法
public void method(){
 if(){}
 if(){
    if(){
    }else{
    }
 }
}

3-如果业务层面上有很明显的if-else区分,那么可以考虑策略模式+注解,或者状态模式+注解的方式,例如本文所讲的方法。
最后放上工程地址,喜欢的麻烦点个star~
策略模式消除if-else

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值