策略模式替代 if else

1. 什么是策略模式?

策略模式其实也是在解耦,把策略的定义、创建、使用这三个部分解耦开来,因为本身策略模式也是基于接口编程,这样其实可以简单的理解客户端调用使用接口进行编程,可以通过工厂方法创建对应的策略模式,进而完成对应的程序功能。这么说起来有点拗口,我们直接看程序吧。

public interface Strategy {
  void alg();
}

public class ConcreteStrategyA implements Strategy {
  @Override
  public void  alg() {
    //具体的算法...
  }
}

public class ConcreteStrategyB implements Strategy {
  @Override
  public void  alg() {
    //具体的算法...
  }
}

策略类的定义比较简单,仅仅包含一个策略接口和一组实现这个接口的策略类。这样客户端就可以根据不同的策略灵活的编程了,通常我们是使用工厂方法根据条件创建不同的策略模式进行使用。

public class StrategyFactory {
  private static final Map<String, Strategy> strategies = new HashMap<>();

  static {
    strategies.put("A", new ConcreteStrategyA());
    strategies.put("B", new ConcreteStrategyB());
  }

  public static Strategy getStrategy(String type) {
    return strategies.get(type);
  }
}

这样就完成了初版的策略模式,其实我们只是使用的 map 进行了策略的路由,如果考虑到开放封闭原则,我们的程序还是存在很大的问题,这里留下一个悬念,下文中的真实例子会解开这个问题。


public class Main {
  public static void main(String[] args) {
    //...
    StrategyFactory factory = new StrategyFactory();
    factory.getStrategy("A").alg();
    //...
  }
}

说到这里其实大家已经看明白了策略模式就是通过接口和一组实现类的方式去掉了大量使用 if-else 的逻辑,那么我们接下来针对实际的例子做一下吧,这样更有体感。

2. 实际例子

编开发的码问(mawen.co)社区目前只支持了 Github 登录,因为 Github 在国外的原因登录起来比较容易超时,于是小编打算增加 Gitee 的登录,这样无论登录还是学习成本都会降低,原来只有 Github 登录的代码逻辑如下,主要逻辑就是通过 Github 的接口获取 Github 用户信息:

AccessTokenDTO accessTokenDTO = new AccessTokenDTO();
String accessToken = githubProvider.getAccessToken(accessTokenDTO);
GithubUser githubUser = githubProvider.getUser(accessToken);
if (githubUser != null && githubUser.getId() != null) {
    //……登录成功逻辑
    return "redirect:/";
} else {
    //……登录失败逻辑
    return "redirect:/";
}

如果简单粗暴的使用 if-else 完成 Gitee 的逻辑接入,那么代码会是如下的样子。

AccessTokenDTO accessTokenDTO = new AccessTokenDTO();
User user;
if(type == "github") {
  String accessToken = githubProvider.getAccessToken(accessTokenDTO);
  user = githubProvider.getUser(accessToken);
} else if(type == "gitee") {
  String accessToken = giteeProvider.getAccessToken(accessTokenDTO);
  user = giteeProvider.getUser(accessToken);
}
if (user != null && user.getId() != null) {
    //……登录成功逻辑
    return "redirect:/";
} else {
    //……登录失败逻辑
    return "redirect:/";
}

那么如果增加 10 个平台呢?代码会越来越多,而且完全违背的开放封闭原则,那么是时候展示策略模式真正的实力了。

首先我们创建一个策略接口和两个策略实现,这里有一个约定,我们平时开发使用了什么设计模式就要以这个设计模式名称结尾,所以具体的改造代码如下。

public interface UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    User getUser(String code,String state);
}

public class GithubUserStrategy implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    public User getUser(String code,String state) {
      // ……根据 Github 信息获取用户资料
    }
}
public class GiteeUserStrategy implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    public User getUser(String code,String state) {
      // ……根据 Gitee 信息获取用户资料
    }
}

同时我们也创建了一个工厂类,把创建具体的策略的实例的逻辑内聚起来。

public class UserStrategyFactory {
   private static final Map<String, UserStrategy> strategies = new HashMap<>();

  static {
    strategies.put("github", new GithubUserStrategy());
    strategies.put("gitee", new GiteeUserStrategy());
  }

  public static UserStrategy getStrategy(String type) {
    return strategies.get(type);
  }
}

我们重新回到增加 Gitee 的代码逻辑,修改成如下样子,是不是清爽了很多?

String type = "";
UserStrategy userStrategy = userStrategyFactory.getStrategy(type);
User user = userStrategy.getUser(code,state);
if (user != null && user.getId() != null) {
    //……登录成功逻辑
    return "redirect:/";
} else {
    //……登录失败逻辑
    return "redirect:/";
}

这时候我们细细品味还是有瑕疵的,因为 UserStrategyFactory 中并没有解决开放封闭原则的根本问题,每次增加新的 UserStrategy 实现类还是需要修改代码的,那么我们继续往前走一步,把具体的判断是否匹配策略的逻辑内聚到策略里面来解决这个问题。

我们给 UserStrategy 增加一个方法,叫做 getSupportedType,用于返回当前策略支持的 type 类型,我们直接用代码说话。

public interface UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    User getUser(String code,String state);
    String getSupportedType();
}

public class GithubUserStrategy implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    public User getUser(String code,String state) {
      // ……根据 Github 信息获取用户资料
    }
    
    public String getSupportedType(){
      return "github";
    }
}
public class GiteeUserStrategy implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    public User getUser(String code,String state) {
      // ……根据 Gitee 信息获取用户资料
    }
    
    public String getSupportedType(){
      return "gitee";
    }
}

然后简单修改一下工厂类

public class UserStrategyFactory {
   List<UserStrategy> strategies = new ArrayList(){{
     this.add(new GithubUserStrategy());
     this.add(new GiteeUserStrategy());
   }};
   
  public static UserStrategy getStrategy(String type) {
    for (UserStrategy userStrategy : strategies) {
      if(type == userStrategy.getSupportedType()) {
        return userStrategy;
      }
    }
  }
}

这样以后,如果增加了新的策略实现,我们只需要增加到 strategies 里面就可以解决问题了,但是如何动态的增加进去不需要修改代码呢?我们可以使用自定义注解,把所有标记自定义注解的类都加载进来或者使用 Spring 的 @Autowired 也会自动的把所有的子类注入到对应的父类集合里面,那么这样是不是就大功告成了呢?

另一种方式 使用枚举 作为key 对象作为value 存入Map中,
使用时 map.get参数为枚举 也可以返回

 class User {
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public User(Long id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                '}';
    }
}



@Component
public interface UserStrategy {

        // 通过 code 和 state 获取对应平台的用户信息
        User getUser(String code, String state);

        String getSupportedType();


}




@Component
public class GiteeUserStrategy implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    @Override
    public User getUser(String code, String state) {
        // ……根据 Gitee 信息获取用户资料
        User user = new User(2L);
        return user;
    }

    @Override
    public String getSupportedType(){
        return "gitee";
    }
}


@Component
public class GithubUserStrategy  implements UserStrategy {
    // 通过 code 和 state 获取对应平台的用户信息
    @Override
    public User getUser(String code, String state) {
        // ……根据 Github 信息获取用户资料

        return  new User(1L);
    }

    @Override
    public String getSupportedType(){
        return "github";
    }
}


@Component
public class UserStrategyFactory {

/** 
 	第一种 方式获取全部子类
	**/
    @Autowired
    List<UserStrategy> strategies ;

 static   Map<String, UserStrategy> map;

    public  UserStrategy getStrategy(String type) {
        if (map == null){
            getStrategy();
        }else {
            System.out.println(111);
        }
        for (UserStrategy userStrategy : strategies) {
            if(type == userStrategy.getSupportedType()) {
                return userStrategy;
            }
        }
        return null;
    }

    @Autowired
    private ApplicationContext ac;

/** 
 	第二种 方式获取全部子类
	**/
    public void getStrategy(){
        //获取继承了settingsCrudBiz的所有子类
        Map<String, UserStrategy> settings = ac.getBeansOfType(UserStrategy.class);
        map = settings;
    }

}


    @Autowired
    UserStrategyFactory userStrategyFactory;
    @GetMapping("/getUser")
    public String testRedis() {

        String code = "";
        String state="";
        String type = "gitee";
        UserStrategy userStrategy = userStrategyFactory.getStrategy(type);
        User user = userStrategy.getUser(code,state);
        if (user != null && user.getId() != null) {
          return  user.toString();
        }

        return  "112243";
    }
}

以下是一个简单的示例,说明如何使用策略模式来替代if else语句: 假设我们现在有一个计算器程序,能够对两个数字进行加、减、乘、除四种运算。如果我们使用if else语句来实现这个程序,代码可能如下所示: ```python class Calculator: def calculate(self, operation, num1, num2): if operation == "add": return num1 + num2 elif operation == "subtract": return num1 - num2 elif operation == "multiply": return num1 * num2 elif operation == "divide": return num1 / num2 ``` 在这个代码中,我们使用了if else语句来根据不同的运算符执行不同的操作。如果我们想要添加更多的运算符,就需要修改calculate方法中的if else语句,这会导致代码变得越来越复杂和难以维护。 现在,我们可以使用策略模式来重构这个代码。首先,我们定义一个抽象的运算策略接口: ```python class OperationStrategy: def execute(self, num1, num2): pass ``` 然后,我们定义具体的加、减、乘、除策略类,它们都实现了这个接口: ```python class AddStrategy(OperationStrategy): def execute(self, num1, num2): return num1 + num2 class SubtractStrategy(OperationStrategy): def execute(self, num1, num2): return num1 - num2 class MultiplyStrategy(OperationStrategy): def execute(self, num1, num2): return num1 * num2 class DivideStrategy(OperationStrategy): def execute(self, num1, num2): return num1 / num2 ``` 最后,在计算器类中,我们使用一个字典来存储不同运算符对应的策略对象,然后根据运算符从字典中获取对应的策略对象,从而执行相应的计算操作: ```python class Calculator: def __init__(self): self.strategies = { "add": AddStrategy(), "subtract": SubtractStrategy(), "multiply": MultiplyStrategy(), "divide": DivideStrategy() } def calculate(self, operation, num1, num2): strategy = self.strategies.get(operation) if strategy is None: raise ValueError("Invalid operation") return strategy.execute(num1, num2) ``` 在这个代码中,我们使用字典来将运算符与对应的策略对象进行映射。当需要添加新的运算符时,我们只需要添加一个新的策略类,并在字典中添加对应的映射关系即可,而不需要修改calculate方法。这样就避免了使用if else语句来处理不同的运算符,提高了代码的可读性、可维护性和可扩展性。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值