设计模式之美

目录

[TOC]

SOLID

​ 由5个原则组成

S(单一职责)

英文:Single Responsibility Principle

如何判断是否满足单一:

  • 主观判断

    • 不同应用场景、不同需求阶段下,判断的依据不同,需要动态分析
  • 客观判断(也有很大主观性)

    • 类、属性、函数、代码过多(过多这个词就有很大主观性,需要分析和一定经验)
    • 依赖过多、私有方法过多
    • 类很难取名或者类中的大量方法都是操作某几个属性

O(开闭)

英文:Open Closed Principle

如何理解和实现“多扩展开放,对修改关闭”:

  • 代码可以让其他人不通过修改原有代码,就能够增加新的功能,这就是扩展性强的表现;平时多注意和学习扩展、抽象、封装这些知识,常说的设计模式,很多也是为了满足开闭而出现的

L(里式替换)

英文:Liskov Substitution Principle

在继承关系中,子类的设计要满足可以替换掉父类,且不改变原有的逻辑和正确性

I(接口隔离)

英文:Interface Segregation Principle

调用者不应依赖到不需要的接口

D(依赖反转)

英文:Inversion Of Control

  • 控制反转:原来由自己控制整个流程执行,交给框架后由框架来进行控制
  • 依赖注入:不通过new创建对象,而通过构造函数或函数传参等方式传递给类使用
  • 依赖反转:高层模块不应依赖底层模块,而是应该通过抽象进行互相依赖,抽象不应依赖具体实现细节,具体实现细节依赖抽象

KISS

英文:Keep It Short and Simple

尽量保持简单

YAGNI

英文:Yor Ain't Gonna Need It

不要过度设计

DRY

英文:Don't Repeat Yourself

不要写重复代码

LOD

英文:The Least Knowledge Principle

迪米特法则/最小知识原则:

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口

设计模式

创建型(4)

单例模式

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
//1.构造函数私有化
private IdGenerator() {
}
//2.静态化参数-同时赋值
private static final IdGenerator instance = new IdGenerator();
//3.提供公共方法供读取
public static IdGenerator getInstance(){
return instance;
}
public long getId(){
return id.incrementAndGet();
}
}

​ 优点:可以将初始化提前,帮助提早发现问题

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
//1.构造函数私有化
private IdGenerator() {
}
//2.静态化参数-延迟赋值
private static IdGenerator instance = null;
//3.提供公共方法供读取
public static synchronized IdGenerator getInstance(){
if( instance == null){
return new IdGenerator();
}
return instance;
}
public long getId(){
return id.incrementAndGet();
}
}

懒汉式(双重检测)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class IdGenerator {
private volatile AtomicLong id = new AtomicLong(0);
//1.构造函数私有化
private IdGenerator() {
}
//2.静态化参数-延迟赋值
private static IdGenerator instance = null;
//3.提供公共方法供读取
public static IdGenerator getInstance(){
if( instance == null){
synchronized (IdGenerator.class){
if(instance == null){
return new IdGenerator();
}
}
}
return instance;
}
public long getId(){
return id.incrementAndGet();
}
}

​ 优点:锁的范围更小,并发量更大

上面的id加上了volatile修饰符,作用是防止指令重排序,参数 = new 对象()有三步

  1. 分配空间(对象的大小)
  2. 空间赋值(对象的内容)
  3. 将空间地址赋值给参数(对象的空间地址给参数)

而在实际执行过程中,可能1直接到3,然后在2执行前,就使用了其中的方法,导致运行错误

​ 新版本JDK内部已经将1、2、3进行了原子化处理,不用再加volatile修饰符了

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IdGenerator {
private volatile AtomicLong id = new AtomicLong(0);
//1.构造函数私有化
private IdGenerator() {
}
//2.静态内部类
private static class SingleHolder{
private static IdGenerator instance = new IdGenerator();
}
//3.提供公共方法供读取
public static IdGenerator getInstance(){
return SingleHolder.instance;
}
public long getId(){
return id.incrementAndGet();
}
}

​ 优点:和懒汉式(双重检测)功能一样,但是更简洁

枚举类

1
2
3
4
5
6
7
public enum IdGenerator {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId(){
return id.incrementAndGet();
}
}

​ 优点:更简洁!

枚举类实现单例的原理

工厂模式

简单工厂

例如:设计一个消息发送功能模块,根据不同行为,创建不同的发送服务进行调用

前置步骤-创建接口
1
2
3
public interface SendInterface {
void send(String receiver,String message);
}
前置步骤-创建不同实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//邮件
public class EmailService implements SendInterface{
@Override
public void send(String receiver, String message) {
System.out.println("发送给"+receiver+",信息:"+message);
}
}
//站内信
public class InnerService implements SendInterface{
@Override
public void send(String receiver, String message) {
System.out.println("发送给"+receiver+",信息:"+message);
}
}
//短信
public class SmsService implements SendInterface{
@Override
public void send(String receiver, String message) {
System.out.println("发送给"+receiver+",信息:"+message);
}
}
前置步骤-逻辑处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Notifacation {
public void send(String type,String receiver,String message){
SendInterface sendInterface = null;
if("email".equals(type)){
//邮件
sendInterface = new EmailService();
}else if("sms".equals(type)){
//短信
sendInterface = new SmsService();
}else if("inner".equals(type)){
//站内信
sendInterface = new InnerService();
}else {
throw new RuntimeException("发送类型错误");
}
sendInterface.send(receiver,message);
}
}

​ 上面代码是我实际处理消息模块的实现思路,根据不同的type动态调用不同的实现类

简单工厂-不带缓存

上面逻辑处理的部分,可以将对象的创建独立出来,就变成简单工厂的方式了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Notifacation {
public void send(String type,String receiver,String message){
SendInterface sendInterface = createSendInterfaceFactory(type);
sendInterface.send(receiver,message);
}
private SendInterface createSendInterfaceFactory(String type){
if("email".equals(type)){
//邮件
return new EmailService();
}else if("sms".equals(type)){
//短信
return new SmsService();
}else if("inner".equals(type)){
//站内信
return new InnerService();
}else {
throw new RuntimeException("发送类型错误");
}
}
}
简单工厂-带缓存

上面处理每次调用会创建新的对象,调用比较频繁会占用大量资源,可以创建后缓存起来,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Notifacation {
private static final Map<String,SendInterface> cacheMap = new HashMap<>();
static {
cacheMap.put("email",new EmailService());
cacheMap.put("sms",new SmsService());
cacheMap.put("inner",new InnerService());
}
public void send(String type,String receiver,String message){
SendInterface sendInterface = createSendInterface(type);
sendInterface.send(receiver,message);
}
private SendInterface createSendInterface(String type){
return cacheMap.get(type);
}
}

工厂方法

作用:将创建实现类抽离出去

创建工厂方法接口
1
2
3
public interface SendInterfaceFactory {
SendInterface createSendInterface();
}
创建实现类工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//邮件工厂
public class EmailServiceFactory implements SendInterfaceFactory{
@Override
public SendInterface createSendInterface() {
return new EmailService();
}
}
//站内信工厂
public class InnerServiceFactory implements SendInterfaceFactory{
@Override
public SendInterface createSendInterface() {
return new InnerService();
}
}
//短信工厂
public class SmsServiceFactory implements SendInterfaceFactory{
@Override
public SendInterface createSendInterface() {
return new SmsService();
}
}
逻辑处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Notifacation {
public void send(String type,String receiver,String message){

SendInterfaceFactory sendInterfaceFactory = null;
if("email".equals(type)){
//邮件
sendInterfaceFactory = new EmailServiceFactory();
}else if("sms".equals(type)){
//短信
sendInterfaceFactory = new SmsServiceFactory();
}else if("inner".equals(type)){
//站内信
sendInterfaceFactory = new InnerServiceFactory();
}else {
throw new RuntimeException("发送类型错误");
}
SendInterface sendInterface = sendInterfaceFactory.createSendInterface();
sendInterface.send(receiver,message);
}
}

​ 可以看到,这里只是将类的创建抽象出去,但是没有干掉if-else

逻辑处理(优化-去掉if-else)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Notifacation {
private static final Map<String,SendInterfaceFactory> mapFactory = new HashMap<>();
static {
mapFactory.put("email",new EmailServiceFactory());
mapFactory.put("sms",new SmsServiceFactory());
mapFactory.put("inner",new InnerServiceFactory());
}
public void send(String type,String receiver,String message){
SendInterfaceFactory sendInterfaceFactory = createSendInterfaceFactory(type);
SendInterface sendInterface = sendInterfaceFactory.createSendInterface();
sendInterface.send(receiver,message);
}
private SendInterfaceFactory createSendInterfaceFactory(String type){
return mapFactory.get(type);
}
}

抽象工厂

作用:对多种情况下的优化,例如出现需要根据配置文件的方式进行消息的发送

1
2
3
4
5
public interface SendInterfaceFactory {
SendInterface createSendInterface();
//增加
SendConfigInterface createSendConfigInterface();
}

​ 增加后,所有工厂类需要实现此抽象方法,即每个工厂类都可以创建两个处理相同事情,但是不同的处理方式的实现类

建造者模式

解决了什么问题

原代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {
private String name;
private Integer age;
private String address;
private Boolean isStudent;
public Person(String name, Integer age, Boolean isStudent) {
this.name = name;
this.age = age;
this.isStudent = isStudent;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
if(address == null || address.length() > 20){
throw new RuntimeException("地址参数设置错误");
}
this.address = address;
}
}

对于一般实体类的属性值设置,采取的方式是:1.必传字段通过构造函数传入2.非必填字段通过

存在的问题:

  1. 如果必传字段很多,则构造函数会太长,而如果不通过构造函数传入,而是通过set方法赋值,则在没调用set方法的情况下,必传字段无法进行校验
  2. 如果属性值有依赖关系,例如isStudent是true,则必须传地址,也没有办法处理
  3. 如果要求对象是不可变对象时,即创建后,不能暴露出set方法

通过建造者模式优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class Person {
private String name;
private Integer age;
private String address;
private Boolean isStudent;

public Person(PersonBuilder personBuilder) {
this.name = personBuilder.name;
this.age = personBuilder.age;
this.address = personBuilder.address;
this.isStudent = personBuilder.isStudent;
}

public static class PersonBuilder{
private static final Boolean DEFAULT_IS_STUDENT = true;
private String name;
private Integer age;
private String address;
private Boolean isStudent = DEFAULT_IS_STUDENT;

public Person build(){
//build的时候进行参数校验,依赖关系校验等
if(this.isStudent == true){
if(address == null){
throw new RuntimeException("学生必须有地址");
}
}
return new Person(this);
}

public PersonBuilder setName(String name) {
if(!StringUtils.hasText(name)){
throw new RuntimeException("姓名必须不为空");
}
this.name = name;
return this;
}

public PersonBuilder setAge(Integer age) {
if(age == null){
throw new RuntimeException("年龄必须不为空");
}
this.age = age;
return this;
}

public PersonBuilder setAddress(String address) {
this.address = address;
return this;
}

public PersonBuilder setStudent(Boolean student) {
isStudent = student;
return this;
}
}

}

使用:

1
2
3
4
5
6
Person person = new Person.Builder()
.setName("张三")
.setAge(18)
.setStudent(true)
.setAddress("我有地址")
.build();

lombok形式

再次优化成lombok的注解@Builder的形式

Person类中增加如下方法

1
2
3
public static Person.PersonBuilder builder(){
return new Person.PersonBuilder();
}

使用:

1
2
3
4
5
6
Person person = Person.builder()
.setName("张三")
.setAge(18)
.setStudent(true)
.setAddress("我有地址")
.build();

​ 可以看到,只要再给静态内部类的set相关方法名换一下,就可以做到和lombok的@builder注解完全一致的效果

@Accessors的使用

Accessors注解有三个属性

  • fluent:会将set前缀去掉,例如原来方法名是setName,加上后就变成name
  • chain:set方法将返回this代替原本的void

使用@Accessors@Setter配合可以很好的简化原来静态内部类的代码,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Person {
private String name;
private Integer age;
private String address;
private Boolean isStudent;

public Person(PersonBuilder personBuilder) {
this.name = personBuilder.name;
this.age = personBuilder.age;
this.address = personBuilder.address;
this.isStudent = personBuilder.isStudent;
}
public static Person.PersonBuilder builder(){
return new Person.PersonBuilder();
}
@Setter
@Accessors(fluent = true,chain = true)
public static class PersonBuilder{
private static final Boolean DEFAULT_IS_STUDENT = true;
private String name;
private Integer age;
private String address;
private Boolean isStudent = DEFAULT_IS_STUDENT;

public Person build(){
//build的时候进行参数校验,依赖关系校验等
if(this.isStudent == true){
if(address == null){
throw new RuntimeException("学生必须有地址");
}
}
return new Person(this);
}
}
}

简化后使用方式:

1
2
3
4
Person person = Person.builder()
.name("张三")
.age(18)
.build();

原型模式

说明

利用已有对象进行复制的方式,来创建新对象,达到节省空间及时间的方式,叫做原型模式,需要理解下浅拷贝和深拷贝。在复制时可以采取浅拷贝老对象得到新对象,新对象指向老对象指向的的散列值,然后新对象对需要更新的内部值进行移除,再添加(移除指向散列值的引用,再添加新引用,则不会影响到老对象指向的散列值),而如果直接修改,会对老对象指向的散列值造成影响,不可取。

结构型(7)

代理模式

作用:将业务代码与非业务代码进行分离

常用场景:监控、统计、鉴权、限流、事务、幂等

详细介绍请点击此处

桥接模式

  • 理解方式一:将抽象和实现解耦,让它们可以独立变化
  • 理解方式二:通过组合代替继承关系,避免继承层次的指数级爆炸

实例

JDBC驱动实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class.forName("com.mysql.jdbc.Driver");

String url = "jdbc:mysql://119.91.255.95/blog?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true";

Connection connection = DriverManager.getConnection(url);

Statement statement = connection.createStatement();

String queryStr = "select * from user";

ResultSet resultSet = statement.executeQuery(queryStr);

while (resultSet.next()){
resultSet.getString(1);
resultSet.getInt(2);
}

装饰器模式

适配器模式

门面模式

组合模式

享元模式

行为型(11)

观察者模式

模板模式

策略模式

职责链模式

状态模式

迭代器模式

访问者模式

备忘录模式

命令模式

解释器模式

中介模式