最近在学习spring源码,发现里面的设计模式的应用对工作中写出高质量的代码很有帮助,现在通过一个例子来说明怎么消除代码中的if else 来达到高质量的代码,顺便可以装一下(__) 嘻嘻……
需求:
现在有三种支付方式 微信支付(WECHAT_PAY),谷歌支付(GOOGLE_PAY), 支付宝支付(ALIPAY),现在根据客户端传入的方式来选择对应的支付.
现在来展示常规写法(小白写法)
/**支付接口*/
public interface Payment {
void pay();
}
/**各种支付*/
@Slf4j
@Component
public class Alipay implements Payment {
@Override
public void pay() {
log.info("alipay 支付完成");
}
}
@Slf4j
@Component
public class GooglePay implements Payment {
@Override
public void pay() {
log.info("Google Pay 支付完成");
}
}
@Slf4j
@Component
public class WechantPay implements Payment {
@Override
public void pay() {
log.info("Wechat Pay 支付完成");
}
}
public enum PaymentMethod {
/**支付枚举*/
WECHAT_PAY, GOOGLE_PAY, ALIPAY;
}
@Service
public class PaymentService {
@Autowired
private Alipay alipay;
@Autowired
private GooglePay googlePay;
@Autowired
private WechantPay wechantPay;
public void pay(PaymentMethod paymentMethod) {
switch (paymentMethod) {
case ALIPAY:
alipay.pay();
break;
case GOOGLE_PAY:
googlePay.pay();
break;
case WECHAT_PAY:
wechantPay.pay();
break;
default:
throw new RuntimeException("不支持该种支付方式");
}
}
}
客户端支付
@SpringBootTest
class PaymentTest {
@Autowired
private PaymentService paymentService;
@Test
void paymentClient() {
// 支付方式
paymentService.pay(PaymentMethod.ALIPAY);
}
}
支付结果:
上面代码有一个支付接口,三种支付方式,支付路由为PaymentService中的switch方法(这里和if else一样的意思), 我们可以看到paymentService引入了三种支付的实现类,假如我们现在引入了银联支付,那么我们的paymentService也需要跟着进行修改.怎么解决这个问题,很多人想到了策略模式,每个支付对应一个策略,然后在去实现,策略模式的实现,我这里就不在进行展开了,网上有很多教程.现在来介绍我的实现.
注解方式实现
我们的目的是写一个通用的实现来解决类似的问题,不只是支付方式的问题,所有switch-case的问题基本都可以用这中方式来解决,先看代码实现.
这里用到了模板方法模式,这个类主要是提供applicationContext
来操作spring容器的Bean通用方法
public abstract class AbstractSparrowContext implements ApplicationContextAware, InitializingBean {
protected ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* <p> Title: getBean
* <p> Description: 获取spring容器中的对象
*
* @param targetClz 类
*
* @return T
*
* @author yousuf 2020/2/22
*
*/
@SuppressWarnings("unchecked")
protected <T> T getBean(Class<T> targetClz) {
T beanInstance = null;
//byType
try {
beanInstance = applicationContext.getBean(targetClz);
} catch (Exception ignored) {
}
//byName
if (beanInstance == null) {
String simpleName = targetClz.getSimpleName();
simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
beanInstance = (T) applicationContext.getBean(simpleName);
}
return beanInstance;
}
/**
* <p> Title: getBeanMapByAnnotation
* <p> Description: 获取注解的类
*
* @param annotationClz 注解
*
* @return java.util.Map<java.lang.String,java.lang.Object>
*
* @author zhangshuai 2019/11/6
*
*/
protected Map<String, Object> getBeanMapByAnnotation(Class<? extends Annotation> annotationClz) {
return applicationContext.getBeansWithAnnotation(annotationClz);
}
}
继续抽象一个类出来根据注解来封装需要的Bean.
public abstract class AbstractSparrowAnnotationBeanMap<A extends Annotation,B> extends AbstractSparrowContext {
/**
* <p> Title: getAnnotation
* <p> Description: 需要缓存的注解类型
*
* @return java.lang.Class<A>
*
* @author yousuf 2020/2/24
*
*/
public abstract Class<A> getAnnotation();
/**
* <p> Title: refresh
* <p> Description: 暴露给实现类去操作刷新earlyBeans的接口
*
* @author yousuf 2020/2/24
*
*/
public abstract void refresh(Map<A, B> annotationBeanMap);
@Override
@SuppressWarnings("unchecked")
public void afterPropertiesSet() throws Exception {
Map<A, B> annotationBeanMap = Maps.newHashMap();
Map<String, Object> beanMap = getBeanMapByAnnotation(getAnnotation());
beanMap.values().forEach(bean -> {
A annotation = (A) AnnotationUtils.findAnnotation(value.getClass(), getAnnotation());
annotationBeanMap.put(annotation, (B) bean);
});
refresh(annotationBeanMap);
}
}
解释一下上面代码的用途 :
getAnnotation()
获取注解,这个有具体的实现类来提供.refresh()
用于实现类操纵annotationBeanMap
缓存,获取符合自己需求的BeanafterPropertiesSet()
spring框架的方法,用于初始化数据.这里根据注解找到对应的Bean,并缓存到annotationBeanMap
,key
值为注解类,value
为Bean
到此框架层面的代码已经写完, 这个方法可以用来解决这一类问题,现在来展示通过上面的模板方法来解决文章开头提到的需求.
- 新增一个注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pay {
PaymentMethod method();
}
- 在各种支付方式上加上该注解
@Slf4j
@Component
@Pay(method = PaymentMethod.ALIPAY)
public class Alipay implements Payment {
@Override
public void pay() {
log.info("alipay 支付完成");
}
}
- 新增一个子类来实现
AbstractSparrowAnnotationBeanMap
@Component
public class PaymentHandler extends AbstractSparrowAnnotationBeanMap<Pay, Payment> {
private static final Map<PaymentMethod, Payment> PAY_MAP = Maps.newHashMap();
@Override
public Class<Pay> getAnnotation() {
return Pay.class;
}
@Override
public void refresh(Map<Pay, Payment> annotationBeanMap) {
annotationBeanMap.forEach((pay, payment) -> PAY_MAP.put(pay.method(), payment));
}
public static void pay(PaymentMethod method) {
Payment payment = PAY_MAP.get(method);
payment.pay();
}
}
到此所有代码写完,我们来测试一下我们的代码是否正确,在PaymentService中新增一个方法
public void annotationPay(PaymentMethod paymentMethod) {
PaymentHandler.pay(paymentMethod);
}
测试结果
总结:
通过模板方法模式归纳了一类问题的通用解决办法,去除了switch-case,最主要的是当有新的支付方式接入的时候只需要新增该支付方式的实现类,而不需要修改支付服务(paymentService),这样正是***对修改关闭,对扩展开放***的最好体现.