一、背景1.1 反面教材
(
) {
(
) {
(
) {
(
) {
(
) {
(
) { } } } } }}
2.2 亲历的重构
public Double commonMethod(Integer type, Double amount) {
(3 == type) {
(
) {
}
0.00; }
(2 == type) {
6.66; }
(1 == type) {
8.88; }
(0 == type){
9.99; }
IllegalArgumentException(
);}
2.3 追根溯源
- 我们来分析下代码多分支的原因
- 业务判断
- 空值判断
- 状态判断
- 如何处理呢?
- 在有多种算法相似的情况下,利用策略模式,把业务判断消除,各子类实现同一个接口,只关注自己的实现(本文核心);
- 尽量把所有空值判断放在外部完成,内部传入的变量由外部接口保证不为空,从而减少空值判断(可参考);
- 把分支状态信息预先缓存在Map里,直接get获取具体值,消除分支(本文也有体现)。
- 来看看简化后的业务调用
CalculationUtil.getFee(type, amount)
serviceFeeHolder.getFee(type, amount)
二、通用部分2.1 需求概括
2.2 会员枚举
用于维护会员类型。
MemberEnum { ORDINARY_MEMBER(0,
), JUNIOR_MEMBER(1,
), INTERMEDIATE_MEMBER(2,
), SENIOR_MEMBER(3,
), ;
code; String desc; MemberEnum(
code, String desc) {
.code = code;
.desc = desc; } public int getCode() {
code; } public void setDesc(int code) {
.code = code; } public String getDesc() {
desc; } public void setDesc(String desc) {
.desc = desc; }}2.3 定义一个策略接口
- compute(Double amount):各计费规则的抽象
- getType():获取枚举中维护的会员级别
interface FeeService {
Double compute(Double amount);
Integer getType();}三、非框架实现3.1 项目依赖
junit
junit
4.12
test
3.2 不同计费规则的实现
- 普通会员计费规则
class OrdinaryMember implements FeeService {
public Double compute(Double amount) {
9.99; }
public Integer getType() {
MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初级会员计费规则
class JuniorMember implements FeeService {
public Double compute(Double amount) {
8.88; }
public Integer getType() {
MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中级会员计费规则
class IntermediateMember implements FeeService {
public Double compute(Double amount) {
6.66; }
public Integer getType() {
MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高级会员计费规则
class SeniorMember implements FeeService {
public Double compute(Double amount) {
0.01; }
public Integer getType() {
MemberEnum.SENIOR_MEMBER.getCode(); }}3.3 核心工厂
class ServiceFeeFactory {
Map<Integer, FeeService> map; public ServiceFeeFactory() {
List<FeeService> feeServices =
ArrayList<>(); feeServices.add(
OrdinaryMember()); feeServices.add(
JuniorMember()); feeServices.add(
IntermediateMember()); feeServices.add(
SeniorMember());
map =
ConcurrentHashMap<>();
(FeeService feeService : feeServices) { map.put(feeService.getType(), feeService); } }
class Holder {
ServiceFeeFactory instance =
ServiceFeeFactory(); }
public static ServiceFeeFactory getInstance() {
Holder.instance; }
public FeeService get(Integer type) {
map.get(type); }}3.4 工具类
class CalculationUtil {
public static Double getFee(int type, Double money) { FeeService strategy = ServiceFeeFactory.getInstance().get(type);
(strategy ==
) {
IllegalArgumentException(
); }
strategy.compute(money); }}
3.5 测试
class DemoTest {
public void test() { Double fees = upMethod(1,20000.00); System.out.println(fees);
Double feee = upMethod(5, 20000.00); } public Double upMethod(Integer type, Double amount) {
CalculationUtil.getFee(type, amount); }}
- 执行结果
8.88java.lang.IllegalArgumentException: please input right value四、Spring Boot 实现
上述方法无非是借助策略模式+工厂模式+单例模式实现,但是实际场景中,我们都已经集成了Spring Boot,这一段就看一下如何借助Spring Boot更简单实现本次的优化。
4.1 项目依赖
org.springframework.boot
spring-boot-starter-test
org.springframework.boot
spring-boot-configuration-processor
true
4.2 不同计费规则的实现
- 普通会员计费规则
class OrdinaryMember implements FeeService {
public Double compute(Double amount) {
9.99; }
public Integer getType() {
MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初级会员计费规则
class JuniorMember implements FeeService {
public Double compute(Double amount) {
8.88; }
public Integer getType() {
MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中级会员计费规则
class IntermediateMember implements FeeService {
public Double compute(Double amount) {
6.66; }
public Integer getType() {
MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高级会员计费规则
class SeniorMember implements FeeService {
public Double compute(Double amount) {
0.01; }
public Integer getType() {
MemberEnum.SENIOR_MEMBER.getCode(); }}4.3 别名转换
思考:程序如何通过一个标识,怎么识别解析这个标识,找到对应的策略实现类?
- application.yml
alias: aliasMap: first: ordinaryMember second: juniorMember third: intermediateMember fourth: seniorMember
- AliasEntity.java
(prefix =
)
class AliasEntity {
HashMap<String, String> aliasMap; public HashMap<String, String> getAliasMap() {
aliasMap; } public void setAliasMap(HashMap<String, String> aliasMap) {
.aliasMap = aliasMap; }
public String getEntity(String desc) {
aliasMap.get(desc); }}
该类为了便于读取配置,因为存入的是Map的key-value值,key存的是描述,value是各级别会员Bean的别名。
4.4 策略工厂
class ServiceFeeHolder {
Map<String, FeeService> serviceFeeMap;
AliasEntity aliasEntity;
public Double getFee(String desc, Double money) {
getBean(desc).compute(money); }
public Integer getType(String desc) {
getBean(desc).getType(); } private FeeService getBean(String type) {
FeeService entStrategy = serviceFeeMap.get(aliasEntity.getEntity(type));
(entStrategy ==
) {
IllegalArgumentException(
); }
entStrategy; }}
- 将 Spring中所有 ServiceFee.java 的实现类注入到Map中,不同策略通过其不同的key获取其实现类;
- 找不到对应的策略的实现类,抛出IllegalArgumentException异常。
4.5 测试
(SpringRunner.class)
class DemoTest {
ServiceFeeHolder serviceFeeHolder;
public void test() {
System.out.println(serviceFeeHolder.getFee(
, 1.333));
System.out.println(serviceFeeHolder.getType(
));
System.out.println(serviceFeeHolder.getType(
)); }}
- 执行结果
8.881java.lang.IllegalArgumentException: please input right value五、总结
- 系统中有很多类,而他们的区别仅仅在于他们的行为不同。
- 一个系统需要动态地在几种算法中选择一种。
5.1 策略模式角色
- Context: 环境类
Context叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化,对应本文的ServiceFeeFactory.java。
- Strategy: 抽象策略类
定义算法的接口,对应本文的FeeService.java。
- ConcreteStrategy: 具体策略类
实现具体策略的接口,对应本文的OrdinaryMember.java/JuniorMember.java/IntermediateMember.java/SeniorMember.java。