代码大家都写过,每个人水平有高也有底的,写出来的代码也参吃不齐,往往大神们写出来的代码不仅简洁,而且看起来超级舒服,今天来领略一次框架级的代码,而且场景绝对是开发中经常遇到的。
先来看一下这个场景,需求就是:根据用户选择的不同银行,计算出不同的价格。
刚接到这个份需求时,你会不会觉得很简单,就几个if判断就可以了,比如这样:
是不是很眼熟,是不是都写过,我自己之前写的都是这种的。
这种场景是我们开发中到的,多流程判断。
以上的代码有什么问题呢?
- 这代码不利于维护,全部耦合在一起,很臃肿。
- 这里有很多的魔法数字,1 2 3可以代表很多的状态
第二个问题好解决,我们可以用常量和枚举来解决。第一个问题呢?思考一下该如何解决。
拿到需求先进行分析:
分析出它们之间的共性和个性。
通过分析我们可以抽离出一个支付的接口,创建Strategy接口
/**
* @author: LKP
* @date: 2019/1/2
*/
public interface Strategy {
/**
* 根据支付渠道类型去调用银行的优惠信息
* @param channelId
* @return
*/
String calRecharge(Integer channelId);
}
接下来就直接上代码,你看完代码就可以很好的理解了。
创建一个银行的实现类:
**
* @author: LKP
* @date: 2019/1/2
*/
@Pay(channelId = 1)
public class ICBCBank implements Strategy {
@Override
public String calRecharge(Integer channelId) {
return "我是ICBC";
}
}
创建@Pay注解
/**
* @author: LKP
* @date: 2019/1/2
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pay {
int channelId();
}
创建一个上下文
/**
* @author: LKP
* @date: 2019/1/2
*/
public class Context {
/**
* 根据上文(渠道ID)得到下文(具体的某个实现类)
* @param channlId
* @return
*/
public String chalRecharge(Integer channlId) throws Exception {
// 我们需要一个类帮我们创建具体的实现类,怎么创建的,我不关心,我的
// 条件就是channlid
StrategyFactory strategyFactory = StrategyFactory.getInstance();
Strategy strategy = strategyFactory.create(channlId);
return strategy.calRecharge(channlId);
}
}
创建一个工厂,这里用到了工厂方法设计模式和单例模式
/**
* @author: LKP
* @date: 2019/1/2
*/
public class StrategyFactory {
private static StrategyFactory strategyFactory = new StrategyFactory();
private StrategyFactory() {
}
public static HashMap<Integer, String> source_map = new HashMap<>();
static {
Reflections reflections = new Reflections("com.study.lkp.demoStudy.CleanCode.pay.impl");
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(Pay.class);
for (Class<?> aClass : classSet) {
Pay pay = aClass.getAnnotation(Pay.class);
source_map.put(pay.channelId(), aClass.getCanonicalName());
}
}
/**
* 具体的创建某个银行实现类的方法
*
* @param channelId
* @return
*/
public Strategy create(int channelId) throws Exception {
//卡在没有map,现在有map
String className = source_map.get(channelId);
Class<?> aClass = Class.forName(className);
return (Strategy) aClass.newInstance();
}
public static StrategyFactory getInstance() {
return strategyFactory;
}
}
代码基本就是这样,接下来就是测试了:
/**
* @author: LKP
* @date: 2019/1/2
*/
public class TestMain {
public static void main(String[] args) throws Exception {
Context context = new Context();
System.out.println(context.chalRecharge(1));
}
}
结果是:
整个流程是没有问题的,接下来我们需要增加一个银行,该怎么实现呢?
很简单,只要复制一份ICBCBank类,修改一下类名,还有注解的channel值
/**
* @author: LKP
* @date: 2019/1/2
*/
@Pay(channelId = 2)
public class CMBBank implements Strategy {
@Override
public String calRecharge(Integer channelId) {
return "我是CMB";
}
}
修改一下main方法,再次测试
运行结果为:
看到这里是不是觉得很方便,每次新增一个银行的时候,我们不用修改原有代码,只需要新增一个实现类就可以了。
特别注意,如果是在Spring框架下使用该架构,那么要特别注意:就是实现类里面使用注解获取的对象是获取不到的,因为它们不会被Spring管理,我们前面都是自己new context()的对象,还有是通过反射new出来的,它们已经脱离了Spring的管理,运行时会出来 Null 异常。
比如在实现类使用了这样的注解
@Resource
@Resource
private ElegantCodeGoodsMapper mapper;
那么这些类的对象是获取不到的,运行时会报 null 异常。
解决方案:
创建一个BeanUtils,将那些类写入到Spring里面去
/**
* @author: LKP
* @date: 2019/1/2
*/
@Service
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanUtils.applicationContext = applicationContext;
}
/**
* 把继承该类的 类成员变量 (通过spring 管理的bean)注入到继承的类中去
*/
public BeanUtils() {
//加载继承该类的类,扫描成员变量
Reflections reflections = new Reflections(this.getClass(), new
FieldAnnotationsScanner());
//将所有含有Resource注解的成员变量 扫描出来
Set<Field> fields = reflections.getFieldsAnnotatedWith(Resource.class);
//循环遍历成员变量
for (Field field : fields) {
//循环的目的就是把这些为null的不被spring管理的注入对象,手动从applicationContext 取出来,
//然后设置进去。
try {
//获得成员变量的类名
String simpleName = field.getType().getSimpleName();
//因为我们Spring里面管理bean 的name 都是首字母小写的,所以我们需要把首字母转为小写
String beanName = toLowerCaseFirstLetter(simpleName);
//通过beanname 去applicationContext 去获取 beanName 对象
Object bean = applicationContext.getBean(beanName);
if (bean == null) {
return;
}
//我们必须要打破封装
field.setAccessible(true);
//把spring管理的对象 设置到我们反射出来的对象中
field.set(this, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 将首字母变为小写
* @param beanName
* @return
*/
public String toLowerCaseFirstLetter(String beanName){
String newBeanName = beanName.substring(0, 1).toLowerCase() +
beanName.substring(1, beanName.length());
return newBeanName;
}
}
然后我们的实现类在继承BeanUtils工具类
/**
* @author: LKP
* @date: 2019/1/2
*/
@Pay(channelId = 1)
public class ICBCBank extends BeanUtils implements Strategy {
@Override
public String calRecharge(Integer channelId) {
return "我是ICBC";
}
}
这样就可以将我们的对象写入,并交由Spring进行管理了。