欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-04 22:55:53
...

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

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

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