前言
咋说呢,大学期间阅读过很多源码(Aop、Mybatis、Ioc、Spring Mvc…),刚开始看这些源码的时候觉得云里雾里,一个没什么代码量的人突然去接触这种商业帝国级别的成品源码的时候,根本无从下手,这种感觉很难受,但是也庆幸自己熬过了那段难忘且充实的日子,随着自己代码量的慢慢增多,也开始慢慢的融入了自己的一点小思想带入到开发中来。
设计模式精髓个人理解
适配器模式:顾名思义适配器的存在就是为了去适配别人的,别人发现 X 适配器适配自己,于是就叫 X 适配器来帮干活。这个时候有人站出来说了,老哥我懂是懂你说的这些大白话,但是如果用代码怎么来实现呢?通过分析理论,我们可以得知适配器的几大核心要素:
- 适配器如何去适配别人? 答:适配器需要提供一个适配别人的方法 <IsSupport()>
- 如何实现这个适配别人的方法呢? 答:可以通过别人(类属性)中的某个属性适配,或者直接通过别人的类别适配< Instanceof >
- 匹配上了对应的适配器,适配器是如何帮客户干活的呢?答:适配器需要提供一个干活的方法。例如< Production() >
工厂模式: 顾名思义就是一种工厂的运转模式,而且一个正规的工厂一定有这俩个基础的职责存在。
- 工厂里面有很多部门 。(工厂属性)
- 每当有任务过来的时候,工厂可以很快将任务分配到指定部门的手中,让其部门执行生产命令。(工厂行为)
小结:工厂可以直接生产商品(小作坊般的存在,量级太小,用不用工厂模式运转都无所谓)、工厂负责管理部门让其生产商品(正规军,流程职责分明)
直接上正菜(工厂规范接口定义)
就拿玩具工厂举例子。采用函数式约定一下工厂的规范。注意:I,O是泛型,更具泛用性。并且结合了一下适配器模式,方法的返回值是 Adapter。
@FunctionalInterface
public interface Factory<I, O> {
Adapter<I, O> findAdapter(I var1);
}
适配器接口定义
根据上文中的理论定义这么一个适配器接口
public interface Adapter<I, O> {
/**
* 适配器适配哪些数据类型
*/
Boolean isSupport(I input);
/**
* 适配器的类型
*/
List<String> types();
/**
* 适配器真正干活的方法
*/
List<O> production(Map<String, Object> map);
}
抽象工厂定义
定义抽象的工厂,里面实现了一个 FindAdapter 的方法(遍历所有可用的适配器,找到支持处理 I input 数据类型的适配器,没找到返回 null ),将 AvailableAdapters()定义为抽象方法,具体的实现交给子类。
public abstract class AbstractFactory<I, O> implements Factory<I, O> {
public Adapter<I, O> findAdapter(I input) {
return this.availableAdapters().stream().filter((d) -> {
return d.isSupport(input);
}).findFirst().orElse(null);
}
public abstract List<? extends Adapter<I, O>> availableAdapters();
}
Spring加强工厂定义
结合 Spring 中的监听器,通过Spring上下文对象,获取所有适配器类型的 Bean 放到工厂定义的 adapterList属性中。然后重写抽象工厂中的 AvailableAdapters 方法 return 所有注册过的适配器,这样一来无论是什么种类的适配器,都可以在我们编写的工厂中被寻找到。这种写法很妙吧哈哈哈哈。可能很多人会问:如果此时的适配器还没有注册到IOC 容器中咋办呢????
public class SpringAwareFactory<D extends Adapter<I, O>, I, O> extends AbstractFactory<I, O> implements ApplicationListener<ContextRefreshedEvent> {
private final List<D> adapterList = new LinkedList<>();
@Override
public List<? extends Adapter<I, O>> availableAdapters() {
return this.adapterList;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
contextRefreshedEvent.getApplicationContext().getBeansOfType(Adapter.class).values().stream().forEach(d -> {
adapterList.add((D) d);
});
System.err.println(adapterList);
}
}
下图一圈红的地方是具体的监听器的执行入口,我们可以看到他的上一步之行了 this.finishBeanFactoryInitialization(beanFactory) 这个方法,里面会进行注册初始化项目扫描到的所有 Bean(具体的Spring源码不做过多解释)。读者只需知道在监听器里,获取到的所有的 Bean 都是完整的。
玩具工厂的定义
继承 Spring加强工厂即可满足使用,无需扩展其他方法
@Component
public class SpringToyFactory extends SpringAwareFactory {
@Override
public List<? extends Adapter> availableAdapters() {
return super.availableAdapters();
}
}
到此工厂代码堡垒的搭建雏形已经出来了~~~~~~~~~
抽象适配器开发
老道理定义一个万能抽象类,将泛型具象化,实现了 Production 方法,但是具体的生产原料的方法给抽象出去了,具体的实现交给子类了。这样更灵活一点,将代码进行了一个结耦操作。拿个例子举例来说,不论是什么种类的冰棍的生产过程是不是都是一样的,只是说有原料上的不同而已,如果从代码角度上进行优化我们是不是可以将冰棍的生产过程进行一个拆分出来,达到一个解耦的目的。下面的抽象类运用的也是这么一个思想,然后抽象类中的 IsSupport 方法里面利用到了 Types 方法,Types 方法的实现也是交给子类实现的,只是说为其子类封装好了特定的逻辑而已。仔细想想这种思想在生活中是不是也很常见呢?一切本着简化操作的理念,用户只需付出很少的学习成本,就可以实现复杂需求的目标。是不是很人性化化呢哈哈哈
/**
* 具体的生产玩具的逻辑在这,抽象类抽象出去的方法只是一个提供原料的方法
*/
public abstract class AbstractAdapter implements Adapter<InputData, ToyDog>, StockHandler {
@Override
public Boolean isSupport(InputData input) {
return this.types().contains(input.getType());
}
@Override
public List<ToyDog> production(Map<String, Object> map) {
ArrayList<ToyDog> toys = new ArrayList<>();
List<Object> abstractAstocks = this.getStocks(map);
ArrayList<Map<String, Object>> realStocks = new ArrayList<>();
abstractAstocks.stream().forEach(a -> {
Map<String, Object> stock = this.HandlerStock(a);
realStocks.add(stock);
});
realStocks.stream().forEach(realStock -> {
ToyDog toyDog = new ToyDog();
toyDog.setId((String) realStock.get("id"));
toyDog.setName((String) realStock.get("name"));
toyDog.setColor((String) realStock.get("color"));
toys.add(toyDog);
});
return toys;
}
public abstract List<Object> getStocks(Map<String, Object> map);
}
抽象适配器子类
代码逻辑很简单,给自己贴标签,说自己是猫玩具生产部门、只提供生产猫玩具的原料
@Component
public class CatDepartmentAdapter extends AbstractAdapter {
@Override
public List<Object> getStocks(Map<String, Object> params) {
ArrayList<Toy> toys = new ArrayList<>();
ToyCat catone = new ToyCat("猫一", "黄");
ToyCat cattwo = new ToyCat("猫二", "红");
catone.setId("1");
cattwo.setId("2");
toys.add(catone);
toys.add(cattwo);
ArrayList<Object> res = new ArrayList<>();
toys.stream().forEach(a -> {
res.add(a);
});
return res;
}
@Override
public List<String> types() {
ArrayList<String> res = new ArrayList<>();
res.add("cat");
return res;
}
}
提供一个类似于 DispatcherServlet 的 Service
依旧是本着解耦以及流程化开发的原则,一个餐馆开张的几个前提是,具备从业资格证或者最起码提供的午餐也是获得 3C 认证的吧(一本正经的胡说八道),本着这种设计理念,我们也为我们的玩具工厂制定一些规则。于是乎我们开放了这么一个入口,用于生产玩具前的规则校验。就是遍历所有已注册的适配器,无则抛出对应提示,有则执行对应适配器中的方法。
@Service
public class ToyService {
@Autowired
private SpringToyFactory toyFactory;
public List<ToyDog> production(InputData inputData) {
return this.doProduction(inputData, toyFactory);
}
public List<ToyDog> doProduction(InputData inputData, SpringToyFactory springToyFactory) {
Iterator<? extends Adapter> adapters = springToyFactory.availableAdapters().iterator();
do {
Adapter adapter = adapters.next();
Assert.notNull(adapter, "没有注册%s类型的Department", inputData.getType());
if (adapter.isSupport(inputData)) {
return adapter.production(inputData.getParams());
}
} while (adapters.hasNext());
throw new RuntimeException("不支持的" + inputData.getType() + "类型");
}
}
下面的代码是我从 Spring Mvc 源码中摘出来的一小段源码,也是我参考的源代码哈哈哈哈哈,具体的 Spring Mvc 源码分析在我以前的文章中有的。
单元测试
用我们的玩具工厂生产几个只玩具狗试试,可以看到都和预期的结果一样
@Test
void toys() {
InputData inputData = new InputData();
inputData.setType("dog");
List<ToyDog> production = toyService.production(inputData);
production.stream().forEach(p -> {
System.err.println(p.toString());
});
}
附页
将对象转map,具体的实现细节可以参考这篇博客 Spring提供的BeanUtils源码剖析(附手写copyProperties方法)
public interface StockHandler {
default Map<String, Object> handlerStock(Object stock) {
HashMap<String, Object> resMap = new HashMap<>();
try {
PropertyDescriptor[] targetPds = Introspector.getBeanInfo(stock.getClass()).getPropertyDescriptors();
Arrays.stream(targetPds).forEach(targetPd -> {
try {
Object value = targetPd.getReadMethod().invoke(stock);
String key = targetPd.getName();
String name = Character.toLowerCase(key.charAt(0)) + key.substring(1);
resMap.put(name, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
} catch (IntrospectionException e) {
e.printStackTrace();
}
return resMap;
}
}
玩具狗结构
本文技术总结
1: 代码解耦我永远的神:通过接口接口,写default修饰的方法
2: 代码解耦我永远的神:通过工厂设计模式,将对象的创建与使用分离开来
3: 代码解耦我永远的神:通过适配器设计模式,将一个小业务抽离成一个小适配器,用于适配不同业务