工作中可能会用到的设计模式写写

大概从刚学 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 容器来管理,这就是抽象工厂模式

总结一下: 上面说到了 工厂模式、抽象工厂模式

我在工作中遇到的就大概这向种了吧,可能源码中还有一些其它的模式还没熟悉,不过不熟悉的如果不了解应用场景的话,又会很快忘记,所以今天先总结到这了。

友情链接

我的一个工具 sanri-tools , 可以做 kafka 监控(主题,消费组,分区,反序列化) , redis 数据查看(可以反序列化字段信息看到真实数据),数据表管理,代码生成

sanri-tools

我的博客文章大纲

猜你喜欢

转载自blog.csdn.net/sanri1993/article/details/106606108