概述
设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。
大部分设计模式要解决的都是代码的可扩展性问题。
对于灵活多变的业务,需要用到设计模式,提升扩展性和可维护性,让代码能适应更多的变化;
设计模式的核心就是,封装变化,隔离可变性
设计模式解决的问题:
- 创建型设计模式主要解决"对象的创建"问题,创建和使用代码解耦;
- 结构型设计模式主要解决"类或对象的组合或组装"问题,将不同功能代码解耦;
- 行为型设计模式主要解决的就是"类或对象之间的交互"问题。将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。
创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
- 单例模式用来创建全局唯一的对象。
- 工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
- 建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,"定制化"地创建不同的对象。
- 原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。
设计模式关注重点: 了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。
经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。
常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
不常用的有:原型模式。
建造者模式(Builder 模式)
定义:
建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,"定制化"地创建不同的对象。
- 按需要设置不同的可选参数,还可以检验参数,构建一个不可变对象
- 建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态。
java
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
private ResourcePoolConfig(Builder builder) {
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
if (maxIdle > maxTotal) {
throw new IllegalArgumentException("...");
}
if (minIdle > maxTotal || minIdle > maxIdle) {
throw new IllegalArgumentException("...");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("...");
}
this.maxTotal = maxTotal;
return this;
}
...
}
}
// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
.setName("dbconnectionpool")
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12)
.build();
工厂模式对比:
创建方式 | 应用场景 |
---|---|
new | 创建逻辑简单,没有影响实例生成的参数或条件 |
工厂模式 | 根据参数或条件生成不同实例,同一类实例,对象不多 |
Builder模式 | 创建实例时可以设置不同的参数组合,每种组合可以表现不同的行为,实例对象很多 |
- 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
- 建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,"定制化"地创建不同的对象。
使用场景:
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
- 我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
- 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
- 如果我们希望创建不可变对象,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。
在编码期避免创建错误
定义合法参数
java
public enum Color {
RED, BLACK
}
public enum Model {
NORMAL, MAX
}
public enum CameraNum {
TWO, THREE
}
public enum MemorySize {
G128, G256, G512
}
新增适用红色IPhone的参数定义
用于红色IPhone,限定内存范围,避免对象属性创建错误
java
// 用于红色IPhone,限定内存范围
public enum RedMemorySize {
G256 {
@Override public MemorySize getMemorySize() {
return MemorySize.G256;
}
},
G512 {
@Override public MemorySize getMemorySize() {
return MemorySize.G512;
}
};
public abstract MemorySize getMemorySize();
}
定义不同的Builder创建不同的Iphone
java
public class IPhone {
// 成员定义不变
private Color color;
private Model model;
private CameraNum cameraNum;
private MemorySize memorySize;
// 构造器不变
public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
this.color = color;
this.model = model;
this.cameraNum = cameraNum;
this.memorySize = memorySize;
}
// 黑色IPhone Builder
public static final class BlackBuilder {
private MemorySize memorySize;
// 其他参数没得选,只需保留此方法
public BlackBuilder setMemorySize(MemorySize memorySize) {
this.memorySize = memorySize;
return this;
}
public IPhone build() {
// 限定的参数直接传入
IPhone iPhone = new IPhone(Color.BLACK, Model.NORMAL, CameraNum.TWO, this.memorySize);
return iPhone;
}
}
// 红色IPhone Builder
public static final class RedBuilder {
private Model model;
private CameraNum cameraNum;
private MemorySize memorySize;
public RedBuilder setModel(Model model) {
this.model = model;
return this;
}
public RedBuilder setCameraNum(CameraNum cameraNum) {
this.cameraNum = cameraNum;
return this;
}
// 注意这里入参是RedMemorySize,用于限定内存范围只能是256G和512G
public RedBuilder setMemorySize(RedMemorySize memorySize) {
this.memorySize = memorySize.getMemorySize();
return this;
}
public IPhone build() {
// 限定的参数直接传入
IPhone iPhone = new IPhone(Color.RED, this.model, this.cameraNum, this.memorySize);
return iPhone;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("color=").append(color.toString()).append(", ");
builder.append("model=").append(model.toString()).append(", ");
builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
builder.append("memorySize=").append(memorySize.toString());
return builder.toString();
}
}
测试代码
java
public class BuilderDemo {
public static void main(String[] args) {
// 红色专有Builder,在开发时即可避免创建错误对象
IPhone.RedBuilder redBuilder = new IPhone.RedBuilder();
IPhone redPhone = redBuilder.setCameraNum(CameraNum.THREE)
.setMemorySize(RedMemorySize.G512)
.setModel(Model.MAX).build();
// 黑色专有Builder,在开发时即可避免创建错误对象
IPhone.BlackBuilder blackBuilder = new IPhone.BlackBuilder();
IPhone blackPhone = blackBuilder.setMemorySize(MemorySize.G128).build();
System.out.println(redPhone);
System.out.println(blackPhone);
}
}
输出:
java
color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128
可以看到,这时创建的黑色IPhone是正确的。
创建者模式经典范式
- 创建者和要创建对象分离,这样可解耦,职责更清晰。
- 创建者可作为创建对象的内部类(通常是静态内部类),这样更内聚,使得很容易找到创建者。
- 由于不同的条件/参数组合创建出的对象实例有不同行为,错误组合可能导致错误结果时,可以通过不同的Builder来创建对象,在编码时就避免犯错。
- 通过链式创建方式使得代码更简洁。
小结
-
每个设计模式总有不能被替代的用途,如果发现能互相替代时,就用最简单的那种。
-
尽早避免编码犯错能使代码更健壮,纠错成本更低,所以应尽可能的在前期避免错误发生。
-
工厂模式和Builder模式的选择在于是否有很多不同参数组合会影响被创建实例的行为,如果有就使用Builder模式,否则会因为要穷举各种参数组合导致工厂方法膨胀。