写在开始
本文主要介绍博主的一个小工具(其实就是一个轮子 )
一个使用注解,接口来维护一个策略模式绑定关系的小工具~(这样就不用写很多if / else,代码更加美观)
本文最开始先简单讲了一下,一个小需求,我们去使用策略模式进行开发
然后再用我们的小工具来进行编写这个需求~
策略模式使用(策略模式的正常使用)~
我们来举一个例子:
场景:小王今天接到一个需求,是现在要给商场编写一个收银系统,然后商场内目前有三种计算价格的机制
- 普通顾客,原价
- 普通会员,8折
- 黄金会员,7折
…
而且需求方说有可能还会添加更高等级的价格计算机制,白金会员,钻石会员等等
看到这个需求我们是不是就想到在编写这个需求的时候会采用策略模式来进行编写这个需求~
具体来编写一下~
//策略模式接口
public interface Cashier{
//以分为单位,所以返回值是int
int calculatePrice(int price);
}
以下分别是三个策略具体实现(这边只是演示代码就不写的很复杂了)
//普通顾客
public class NormalCashier implements Cashier {
@Override
public int calculatePrice(int price) {
return price;
}
}
//VIP用户
public class VipCashier implements Cashier {
@Override
public int calculatePrice(int price) {
return new BigDecimal("0.8").multiply(new BigDecimal(price)).intValue();
}
}
//黄金VIP用户
public class GoldVipCashier implements Cashier {
@Override
public int calculatePrice(int price) {
return new BigDecimal("0.7").multiply(new BigDecimal(price)).intValue();
}
}
然后我们定义一个枚举
public enum UserType {
/**
* 普通顾客
*/
NORMAL("normal", 0),
/**
* VIP顾客
*/
VIP("vip", 1),
/**
* 黄金VIP顾客
*/
GOLD_VIP("gold_vip", 2);
UserType(String name, Integer value){
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
private String name;
private Integer value;
}
然后定义一个上下文(这里仅仅只是定义了策略模式的上下文,并没有在上下文中结合简单工厂的思想~)
public class Context {
private Cashier cashier;
public Context(Cashier cashier){
this.cashier = cashier;
}
public int calculatePrice(int price){
return cashier.calculatePrice(price);
}
}
具体使用
//因为我们是进行演示,我们就不再创建第二个层级了,就直接逻辑在Controller写了
@ResponseBody
@RequestMapping("/cashier")
public Integer cashier(@RequestParam Integer userType, @RequestParam Integer price){
if (UserType.GOLD_VIP.getValue().equals(userType)){
GoldVipCashier goldVipCashier= new GoldVipCashier();
return new Context(goldVipCashier).calculatePrice(price);
}else if (UserType.VIP.getValue().equals(userType)){
VipCashier vipCashier= new VipCashier();
return new Context(vipCashier).calculatePrice(price);
}else {
NormalCashier normalCashier = new NormalCashier();
return new Context(normalCashier).calculatePrice(price);
}
}
这里我们发现如果我们有很多很多策略模式,我们就发现会有很多很多if else 很不美观
这里我们这个小工具就横空出世啦!!!!
策略模式小工具
github 地址:github地址,点此跳转
依赖地址:
<dependency>
<groupId>io.github.wangzh13</groupId>
<artifactId>strategy</artifactId>
<version>1.0.0</version>
</dependency>
简单的原理介绍
原理很简单
首先我们维护了一个 Map<String, Map<Object, Object>> 结构
我们将这个接口文字化
Map<策略模式接口名, Map<规则值, Bean实例>>
//大概结构是如下
|-----规则 --- bean实例(策略模式具体的实现类)
策略模式接口
|-----规则 --- bean实例(策略模式具体的实现类)
实际就是我们在SpringBoot的时候根据SpringApplicationRunListener以及Bean的后置处理器,扫描所有Bean对象,维护一个绑定关系,将被声明为策略模式的接口,以及他的实现类根据相应的规则绑定起来(这里先大概了解一下,后面会有相关的例子解释)放到这个Map结构里面,使用的时候根据策略模式名称以及相应规则,拿到相应的Bean实例
工作流程
首先大体说一下工作流程,我们要做的事情
最前面:
- SpringBoot 对待Bean的策略一定是单例
(默认是单例,如果没有进行自定义设置过,就无须关心) - 策略模式的实现类一定要交由spring管理(比方说添加@Component 注解)
- 引入项目jar包,jar包地址
<dependency>
<groupId>io.github.wangzh13</groupId>
<artifactId>strategy</artifactId>
<version>1.0.0</version>
</dependency>
- 我们在实现策略模式的时候会编写一个接口,来代表这一类策略模式,我们需要在这个接口上添加@StrategyType注解(注解属性是你想绑定的规则)
- 针对此接口具体的实现类,除去实现这个接口(比方说上文的Cashier接口,VipCashier实现类,需要实现Cashier接口)还要实现一个StrategyEntity< T >(没错这个接口是需要指定泛型的,这里指定的泛型类型应该与我们在第一步的时候声明的注解属性)然后重写 StrategyEntity接口当中的 rule方法(具体使用可以看下面的示例)
- 我们使用一个处理器传入我们的绑定规则与策略模式接口,来匹配到相应的实例类,然后具体使用
//我们需要自动注入StrategyHandler这个类
@Autowired
StrategyHandler strategyHandler;
//然后使用StrategyHandler,调用get方法
//get方法会返回一个根据具体规则的值帮你匹配到的Cashier接口的具体实现类,
//然后我们就可以调用Cashier类的方法
strategyHandler.get(Cashier类接口类型(Class类型参数), 具体规则的值)
小案例(先简单说一下)
@StrategyType(UserType.class)
public interface Cashier {
int calculatePrice(int price);
}
@Component
public class GoldVipCashier implements Cashier, StrategyEntity<UserType> {
@Override
//重写rule方法,返回一个规则集合
//比方说我们这里返回的一个规则集合里面有VIP 与黄金VIP两个枚举值
//那么当我们调用StrategyHandler的get方法时,传入Cashier.class 与 (枚举中,VIP或者GOLD_VIP二者之一)
// StrategyHandler都会返回GoldVipCashier 此策略实现类
public List<UserType> rule() {
List<UserType> list = new ArrayList<>();
list.add(UserType.GOLD_VIP);
list.add(UserType.VIP);
return list;
}
@Override
public int calculatePrice(int price) {
return new BigDecimal("0.7").multiply(new BigDecimal(price)).intValue();
}
}
对上述案例的分析!!!,这里看一下更容易理解
我们分析一下上面代码最后放到我们开始的时候说的Map结构里面对应的地方
--我们把这个结构贴在这里方便我们理解--
Map<String, Map<Object, Object>>
--结构文字化--
Map<策略模式接口名, Map<规则值, Bean实例>>
首先我们在Cashier接口上打上了注解StrategyType
放到map里面就是
Map<Cashier接口类名, Map<规则值, Bean实例>>
然后我们GoldVipCashier类实现了Cashier接口,并且实现了StrategyEntity<UserType>(这两个接口都必须得实现到)
然后我们重写了StrategyEntity<UserType>接口的rule方法
public List<UserType> rule() {
List<UserType> list = new ArrayList<>();
list.add(UserType.GOLD_VIP);
list.add(UserType.VIP);
return list;
}
这个rule接口返回了一个list,这个list里面有两个枚举值UserType.GOLD_VIP,UserType.VIP
当我们写完这个,我们来看看在Map里面上述是什么结构
之前是 Map<Cashier接口类名, Map<规则值, Bean实例>>
现在是
|-----UserType.GOLD_VIP --- GoldVipCashier类
Cashier接口类名
|-----UserType.VIP --- GoldVipCashier类
然后我们使用
StrategyHandler.get(Cashier.class, UserType.GOLD_VIP或者UserType.VIP)取到GoldVipCashier类
我们来重新完整的修改一下上面的例子~(需求不变,使用方法用上我们的小工具)
首先在项目中引入jar包
<dependency>
<groupId>io.github.wangzh13</groupId>
<artifactId>strategy</artifactId>
<version>1.0.0</version>
</dependency>
我们还是定义一个Cashier接口
//在接口上方添加StrategyType注解
//(注解属性是你想绑定的规则,这里我们需要绑定的规则是我们上面定义的枚举类)
@StrategyType(UserType.class)
public interface Cashier{
//以分为单位,所以返回值是int
int calculatePrice(int price);
}
然后三个实现类,实现Cashier接口的同时,再实现StrategyEntity接口,并且重写rule方法,指定每个实现类,在什么枚举值时会被匹配到
//普通顾客
@Component
public class NormalCashier implements Cashier, StrategyEntity<UserType> {
@Override
public List<UserType> rule() {
//根据这个规则显示,当枚举值是UserType.NORMAL时,NormalCashier被匹配到
//可以定义多个规则,但这里的需求很简单,只需要指定一个
List<UserType> list = new ArrayList<>();
list.add(UserType.NORMAL);
return list;
}
@Override
public int calculatePrice(int price) {
return price;
}
}
//VIP客户
@Component
public class VipCashier implements Cashier, StrategyEntity<UserType> {
@Override
public List<UserType> rule() {
//根据这个规则显示,当枚举值是UserType.NORMAL时,VIP被匹配到
//可以定义多个规则,但这里的需求很简单,只需要指定一个
List<UserType> list = new ArrayList<>();
list.add(UserType.VIP);
return list;
}
@Override
public int calculatePrice(int price) {
return new BigDecimal("0.8").multiply(new BigDecimal(price)).intValue();
}
}
//黄金VIP客户
@Component
public class GoldVipCashier implements Cashier, StrategyEntity<UserType> {
@Override
public List<UserType> rule() {
//根据这个规则显示,当枚举值是UserType.GOLD_VIP时,VIP被匹配到
//可以定义多个规则,但这里的需求很简单,只需要指定一个
List<UserType> list = new ArrayList<>();
list.add(UserType.GOLD_VIP);
return list;
}
@Override
public int calculatePrice(int price) {
return new BigDecimal("0.7").multiply(new BigDecimal(price)).intValue();
}
}
然后我们来看Controller层
@Autowired
StrategyHandler strategyHandler;
@ResponseBody
@RequestMapping("/cashier")
public Integer cashier(@RequestParam UserType userType, @RequestParam Integer price){
//这里我们讲解一下
//get方法里面的第一个参数,Cashier.class,这个参数代表,我们这次要匹配到的Cashier接口的具体实现类
//get方法里面的第二个参数,userType,是具体的值,是请求体里面携带的具体参数,
//当请求参数里面值是UserType.NORMAL,他就会返回NormalCashier类Bean对象
//当请求参数里面值是UserType.VIP,他就会返回VipCashier类Bean对象
//当请求参数里面值是UserType.GOLD_VIP,他就会返回GoldVipCashier类Bean对象
//我们说明一下,这个规则是根据我们重写的rule方法返回的List 里面的值~
//我们这句话是进行了简写, 实际是以下两句话
// Cashier cashier = strategyHandler.get(Cashier.class, userType)
// return cashier.calculatePrice(price);
return strategyHandler.get(Cashier.class, userType).calculatePrice(price);
}
注意:
博主推荐大家使用的规则类型为,基本数据类型的包装类型(Integer,Boolean…等),枚举,Class对象作为规则
当然我们这个工具也支持一些自定义bean作为规则,但是可能使用上比较麻烦一些~(需要重写 hashCode 方法 跟 equals方法,目的保证相应的规则绑定对应的值)
其实我们这个原理是维护了一个HashMap,规则是作为key来进行找到相应的Bean实例,所以要重写hashCode与equals方法,保证可以根据相应的规则绑定相应的Bean实例
例子:
用户需求(这里只是简单的举例子)id可以作为唯一标识标明
//声明User类,重写equals方法跟hashCode方法,
//只要id相同我们就认为两个User对象相同
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
if (id == null){
return 1;
}
return id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof User){
return id.equals(((User) obj).getId());
}
return false;
}
}
@StrategyType(User.class)
public interface UserStrategy {
String choice();
}
@Component
public class UserConsumer01 implements UserStrategy, StrategyEntity<User> {
@Override
public List<User> rule() {
List<User> list = new ArrayList<>();
User user = new User();
//当User对象属性ID为1时,匹配当前策略模式
user.setId(1);
list.add(user);
return list;
}
@Override
public String choice() {
return "hhh,我是美团";
}
}
@Component
public class UserConsumer02 implements UserStrategy, StrategyEntity<User> {
@Override
public List<User> rule() {
List<User> list = new ArrayList<>();
User user = new User();
//当User对象属性ID为1890时,匹配当前策略模式
user.setId(1890);
list.add(user);
return list;
}
@Override
public String choice() {
return "hhh,我是饿了么";
}
}
额外的一个小功能(用于开发调试过程当中)
如下:
启动后会在resource目录下创建一个strategy文件,然后创建一个strategy.txt文件
然后文件内打印着我们上文Map结构的文字化(将所有的数据打印出来,为了方便我们调试错误,查看是否绑定正确)~
如下图
具体值的对应关系解释~
模式名称后:是StrategyType注解的接口全类名
具体实现:具体的规则值:具体实现类的全类名
好了,文章大概内容就这样啦,欢迎大家使用哦~