工作中可能会用到的设计模式写写
大概从刚学 Java 的时候就开始学设计模式了,但总是学了忘,忘了学。 其实容易忘记的原因是没有真正的去理解它,然后没有在工作中去用过,因为大部分的时间在处理业务,基本都是增删改查的东西,并且网上大部分的博客或者教程都是模拟场景,并且没有针对业务说明那种场景下可能会出现的问题。
举个例子: 以前学代理模式和装饰模式,都是要引入目标类然后调目标类的方法进行包装,感觉两个模式没区别啊,以现在的眼光来看,静态代理和装饰是没区别,但是动态代理和装饰的区别就大了,例如
- 可以代理 http 的调用,使用者只需一个接口,就可以调用远程的服务 (Feign)
- 可以代理 rpc 调用,提供者提供一个接口,使用者可以像调本地应用一样调用远程服务 (Dubbo )
- 可以在反序列化的时候,对于接口类型生成代理类,而不是报错 (Fastjson)
- 在创建 bean 的时候创建 bean 的增强实现 ( Spring )
那是不是在业务中就一定使用不到设计模式呢,不是,业务还是有场景和设计模式是符合的,但可能不是 100% 契合,需要你找到最合适的设计模式,像我最近碰到的一个业务场景
从 kafka 消费数据,但这个数据的处理过程有点麻烦,第一步需要存储整条消息到 mongodb , 第二步要存储到业务数据表,第三步算出消息要通知到系统中哪些用户(可以是短信通知和弹窗通知),第四步要根据终端设备的状态做对象处理 。 像这种就很适合责任链模式,后面的需求改动证明我的设计模式是对的,后面加了一个需求就是对每个设备发过来的消息有其自定义的数据,需要分别存库 ,我就在责任链中间加了一层处理就解决了,当中遇到了什么问题呢:
- 首先链中的每一个链条需要被 spring 容器管理,那我就只能从 beanFactory 获取整条链,只有一个总的链条而不是多个,其实可以用注解进行辅助,将其分为多个链条;
- 然后下一个问题就是后面发现从上一个链没法向下一个链传递数据,因为我所有的链过程都是用的解析 kafka 的数据,如果当初设计的时候就应该上层可以向下层传递一个 info 或者 warn 数据会更好 ;
- 然后就是异常的处理,某个链出异常了,是需要回滚还是可以继续没有设计好,存在整个链条没有原子性的问题,这样会造成脏数据,回滚可以用另一种设计模式 命令模式,每一个链条又可以看作是一个命令,当这个消费链中有致命错误时,就链进行回退,并将消息存入 kafka 的一个异常队列
在网上看到过一种在业务中会出现的场景,也在这说一下,文章可能找不到了; 就是接入支付有几种渠道 微信、支付宝、银联 , 支付方式有 密码、人脸、指纹 ,如果传统写法就会有 3 * 3 个 if else ,这时这种笛卡尔积的就适合使用桥接模式 渠道聚合支付方式接口,可能不同渠道的支付方式实现会有小差异,可以使用设计模式 转换器 模式实现统一 , 最后代码可能就是这样子的 , 看起来就清爽易读了
// 使用微信的密码支付
new WeixinPayChannel(new PasswordAdapter(new WeixinPasswordPay())).transfer(args...);
总结一下 : 上面说到了常用的几种模式,动态代理、装饰、责任链,桥接,适配器,命令(主要是回退命令)
有一些模式是工作中,你可能无意识就使用到了,但你可能并不知道它是一种设计模式 ,比如 在使用 lombok 后,在一些属性比较多的类的习惯性的加了注解 @Builder ,你就已经使用了构建者模式了,只是这个构建者是 lombok 帮你实现了而已 ;再比如经常会对一些公共的东西往上抽,然后提取出一个类来让子类继承,然后在父类中调抽象方法但这个抽像方法是子类实现的,模板方法 就被你用上了;然后一些业务类,你总是习惯的加上 @Component 注解或 @Service 注解 ,其实这个类默认就是单例 ,单例模式 你也用上了,一些工具类但是使用的时候需要有一些配置,这个配置你可以使用懒加载在第一次使用的时候构建它,也是单例模式的运用; 然后像这样的需求,在应用启动后我要预热缓存,实现 ContextLoaderListener , kafka 来消息了要处理添加 @KafkaListener , Web 容器加载好了或者被销毁的时候要处理一些事情,实现 ServletContextListener 都是 观察者 的应用
总结一下:上面说到了 构建者、模板方法、单例,观察者
有些书上一开始就把单例介绍得很复杂,让看的人一下子就懵逼了,其实你大可以先绕过,你只要只到最简单的两种,直接创建的方式也叫饿汉式还有懒汉式两种即可。
饿汉式:类加载时直接创建实例,适用于创建对象不大并且创建不耗时的场景
public Singleton{
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){return instance;}
}
懒汉式:用的时候才加载,但需要考虑线程安全,需要同步锁
public Singleton{
private Singleton(){}
private static Singleton instance;
public synchronized static Singleton getInstance(){
if(instance == null){instance = new Singleton();}
return instance;
}
}
// 上面的做法效率低下,不管实例有没有创建,每个线程都要获取同步锁,所以有了改进的写法(也叫双重检验锁)
public Singleton{
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
// 需要再判断一次的原因是在进入第一个判断有多个线程,不能重复创建实例
if(instance == null){instance = new Singleton();}
}
}
return instance;
}
}
静态内部类式:利用到了 Java 的类在使用时才会进行加载的原理,即保证了线程安全,又实现了懒加载
public Singleton{
private Singleton(){}
private static class Inner {static Singleton instance = new Singleton();}
public static Singleton getInstance(){return Inner.instance;}
}
以前一直觉得抽象工厂模式有点多余,感觉它把简单的事情搞复杂化了,简单工厂还好理解,抽象是真的不能理解 ,直到遇到了这个需求,实现一个对象池,每次从池中拿一个对象,需要拿未使用的对象,当然我可以借助 common-pool2 来实现,但是如果需要与 spring 结合,我就需要一个创建连接的工厂了,并且把这个工厂给 spring 容器来管理,这就是抽象工厂模式
总结一下: 上面说到了 工厂模式、抽象工厂模式
我在工作中遇到的就大概这向种了吧,可能源码中还有一些其它的模式还没熟悉,不过不熟悉的如果不了解应用场景的话,又会很快忘记,所以今天先总结到这了。
上一篇: JavaScript 数据类型
下一篇: Java线程-线程池-信号量