通常我们说的设计模式,指的是GoF23(Gang of Four),包括23个常用的设计模式。这里尝试从不同的角度聊一聊其中几个设计模式。
1. 单例模式
单例模式可能是一个程序员最早接触的设计模式之一,因为这个设计模式适用的场景非常广泛。比如,在Spring中,我们可以加注@Scope annotation来设置一个bean是”单例“或者”多例“,默认是单例。
单例模式有多种实现方式,其中常见的一种方式,如下:
public class Singleton {
private static Singleton mySingleton;
private Singleton() {
}
public synchronized Singleton getInstance() {
if (null == mySingleton) {
mySingleton = new Singleton();
}
return mySingleton;
}
}
基于这种方式,每次调用getInstance都需要加锁,性能会受到很大的影响。所以,就有了下面这种基于双重检查锁的方式:
public class Singleton {
private static Singleton mySingleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == mySingleton) {
synchronized (Singleton.class) {
if (null == mySingleton) {
mySingleton = new Singleton(); // error
}
}
}
return mySingleton;
}
}
这种(double checked locking)方式,解决了上面提到的性能问题。但是,由于JVM的指令重排序机制,上面的方式在某些情况下可能不工作,具体解释可以参考:
那么,解决方案就是在mySingleton变量申明前加上volatile(JDK1.5之后),如下:
public class Singleton {
private volatile static Singleton mySingleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == mySingleton) {
synchronized (Singleton.class) {
if (null == mySingleton) {
mySingleton = new Singleton();
}
}
}
return mySingleton;
}
}
2. 代理模式
代理,对应的英文单词是”proxy“。提到代理,可能我们首先想到的是如果要访问google,需要设置一个代理,如下:
设置了这个代理之后,我们访问网址的所有请求,都会先经过这个代理服务器,然后由它帮忙转发到相应的网址服务器;处理完成之后,按原路返回给用户。代理服务器在转发请求之前,可能会做一些额外逻辑,比如:权限校验,限制只有付费用户才可以使用这个代理。
除此之外,我们可能还听过一个术语:反向代理reverse proxy,nginx就是一个流行的用于实现反向代理的开源软件;还有,在之前的一篇文章中(这些年我对微服务的理解)提到的API Gateway,其实也实现了reverse proxy的功能。所以,这里的proxy和上面的代理有些类似,都是帮忙转发请求到具体的请求处理服务器,并且对请求做一些额外的处理。
其实,代理模式原理上跟上面的例子类似,从编程角度讲,代理类在执行本来要代理的方法之前或者之后增加一些切面方法,以实现一些额外功能,比如打印日志等。
代理的实现,可以分为静态代理和动态代理。JDK提供了一个Proxy类,用于实现动态代理。Spring的AOP面向切面编程,底层就是基于JDK的动态代理或者CGLIB。
3. 观察者模式
简单讲,可以把观察者模式理解成事件监听机制,当一个事件发生时,触发所有提前注册好的监听方法。比如:Spring JPA的@PostPersist、@PostUpdate,当一个entity被持久化或者更新之后,加注了相应annotation的方法就会被执行。在很早之前用过JMX来实现Java进程的monitoring功能,JMX里面的Notification机制也即是基于观察者模式。还有,当我们点击UI上的一个button按钮,即会触发提前注册的相应callback方法,也是类似的原理。
同时,可以把观察者模式和现在常用的pub-sub模式做类比理解,它们有异曲同工之处。
4. 模版方法模式
我们平时工作中很多地方都有用到模版方法模式,比如:在Spring中,一个Bean的life cycle都会由容器负责执行init和destroy方法,而这两个方法可以在每个Bean定义的时候重写,这和c++里面类的构造方法、析构方法有些类似。同样的,SAP UI5里面的controller的life cycle也都会按顺序执行onInit、onBeforeRendering、onAfterRendering和onExit这几个方法,任何自己写的controller都可以重写。
5. 建造者模式
对于一个包含很多属性的复杂POJO,在创建一个对象的时候,我们调用setter方法去给相应的属性赋值,有时候代码会显得比较冗余,如下:
Person p = new Person();
p.setAttribute1("a1");
p.setAttribute2("a2");
p.setAttribute3("a3");
p.setAttribute4("a4");
这时我们就可以采用建造者模式,如下:
Person p = Person.builder().attribute1("a1").attribute2("a2").attribute3("a3").attribute4("a4").build();
一定程度来说,这种链式编程的方式显得简洁一些。如果使用Lombok,我们可以开箱即用的使用建造者模式,只需要在POJO类上加注一个@Builder annatation即可。
还有一个我们最熟悉的StringBuilder也是应用了这个模式,用于构造一个String对象。
References