巧用策略模式、工厂模式,替换if-else

Posted by Kaka Blog on December 15, 2020

策略模式

首先定义一个接口:

public interface PayService {
    BigDecimal quote(BigDecimal price);
}

定义几个策略类:

@Service
public class VipPayService implements PayService, InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        StrategyFactory.register("vip", this);
    }

    @Override
    public BigDecimal quote(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.8));
    }
}

@Service
public class CommonPayService implements PayService, InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        StrategyFactory.register("common", this);
    }

    @Override
    public BigDecimal quote(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(1.0));
    }
}

策略模式有一个缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。这里引入工厂模式。

工厂模式

定义工厂类:

public class StrategyFactory {
    private static Map<String, PayService> serviceMap = new HashMap<>();
    public static PayService getService(String name) {
        return serviceMap.get(name);
    }

    public static void register(String name, PayService service) {
        Assert.notNull(service, "pay service can't be null");
        serviceMap.put(name, service);
    }
}

测试

定义控制器:

@ApiOperation(value = "计算金额", notes = "计算金额")
@GetMapping("price")
public BigDecimal getPrice(@RequestParam String name) {
    return StrategyFactory.getService(name).quote(BigDecimal.valueOf(100));
}

1、访问:http://localhost:8080/user/price?name=vip,返回:

{
    "code": 0,
    "message": "成功",
    "data": 80
}

2、访问:http://localhost:8080/user/price?name=common,返回:

{
    "code": 0,
    "message": "成功",
    "data": 100
}

优化

策略类需要实现afterPropertiesSet方法,需要暴露StrategyFactory注入接口,这里我们通过修改StrategyFactory类,实现策略类只处理业务逻辑。

@Component
public class StrategyFactory implements InitializingBean, ApplicationContextAware {
    private static Map<String, PayService> serviceMap = new HashMap<>();
    private ApplicationContext context;

    public PayService getService(String name) {
        return serviceMap.get(name);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        context.getBeansOfType(PayService.class)
                .values()
                .forEach(service -> serviceMap.put(service.getName(), service));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}
  • ApplicationContextAware:通过它Spring容器会自动把上下文环境对象调用ApplicationContextAware接口中的setApplicationContext方法。这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。
    • prepareBeanFactory(beanFactory)方法装载ApplicationContextAwareProcessor
    • finishBeanFactoryInitialization方法遍历BeanProccessor调用postProcessBeforeInitialization方法,该方法再调用setApplicationContext将ApplicationContext设置到Aware里面。

StrategyFactory 实现 InitializingBean 接口,在 afterPropertiesSet 方法中,基于 Spring 容器将所有PayService自动注册到serviceMap,Spring容器启动后,getService方法可以直接通过name来获取对应的实现类。

策略类接口:

public interface PayService {
    String getName();
    BigDecimal quote(BigDecimal price);
}

策略类实现类:

@Service
public class VipPayService implements PayService {
    @Override
    public String getName() {
        return "vip";
    }

    @Override
    public BigDecimal quote(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.8));
    }
}

使用:

@Autowired
private StrategyFactory strategyFactory;

@ApiOperation(value = "计算金额", notes = "计算金额")
@GetMapping("price")
public BigDecimal getPrice(@RequestParam String name) {
    return strategyFactory.getService(name).quote(BigDecimal.valueOf(100));
}

总结

本文,我们通过策略模式、工厂模式以及Spring的InitializingBean,提升了代码的可读性以及可维护性,彻底消灭了一坨if-else。

参考