关于设计模式的那些事(一)
通常我们说的设计模式,指的是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的指令重排序机制,上面的方式在某些情况下可能不工作,具体解释可以参考:
- https://www.cnblogs.com/xz816111/p/8470048.html
- https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
那么,解决方案就是在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
- https://docs.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern
- https://projectlombok.org/features/Builder
- Socket学习总结系列(一) -- IM & Socket
- 【腾讯云的1001种玩法】十分钟轻松搞定云架构 之四:替你分心的负载均衡
- 【腾讯云的1001种玩法】十分钟搞定云架构 · 什么是Bucket、什么是Object
- 【腾讯云的1001种玩法】十分钟轻松搞定云架构 · 负载均衡的最佳实践
- 【黑客浅析】像黑客一样思考
- 【腾讯云的1001种玩法】 十分钟轻松搞定云架构 · 负载均衡的几种均衡模式
- ASP.NET Web API的Controller是如何被创建的?
- 【腾讯云的1001种玩法】十分钟轻松搞定云架构:COS的两种上传模式
- 物流行业迎变革,云计算是基础,大数据是关键
- Socket学习总结系列(二) -- CocoaAsyncSocket
- 比特币勒索病毒肆虐,腾讯云安全专家给你支招
- HTML5 直播协议之 WebSocket 和 MSE
- IoC在ASP.NET Web API中的应用
- 跟鹅厂老司机学技术之一:“遇见” Kotlin
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法