相关阅读:
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA基础(三)ClassLoader实现热加载
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则
1. 创建对象实例的方式
Builder模式,工厂模式,new都可以用于创建对象实例,它们三者的应用场景和区别如下:
创建方式 | 应用场景 |
---|---|
new | 创建逻辑简单,没有影响实例生成的参数或条件 |
工厂模式 | 根据参数或条件生成不同实例 |
Builder模式 | 创建实例时可以设置不同的参数组合,每种组合可以表现不同的行为 |
在选择以上三种方式创建对象时,不要考虑A方式能不能替代B方式,因为如果它们只是替代关系,那就没有体现它们各自的价值,只有当一个事物有不可替代性时,才有其价值,所以应该考虑的是:我是不是不得不选择它。
如果A,B互相可替代,那么就使用最简单的方式,比如new能搞定,就不要硬整一个工厂模式,不要为了用模式而用模式。
1.1. 根据参数或条件生成不同实例
如在JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则 中的例子,税策略工厂根据不同的税类型生成了不同的税策略实例去算税。
在这种场景下用new创建实例是不合适的, 不得不使用工厂模式来创建实例。
1.2. 不同的参数组合表现不同的行为
Builder模式创建实例时可以使用不同的参数,使得最后创建的实例有不同的行为,举个例子,IPhone有时针对不同的颜色区分不同的具体机型参数,例如(举例用,不是完全和实际一致):
颜色 | 摄像头个数 | MAX版 | 内存 |
---|---|---|---|
红色 | 2,3个摄像头可选 | 普通版和MAX版 | 256G和512G |
黑色 | 仅有2个摄像头 | 普通版 | 128G,256G和512G |
此时用工厂模式就不合适,因为组合参数有很多种,每种组合都写一个创建方法很冗余,更好的处理方式是在创建实例时可以设置参数,由调用者自行设置需要的参数。
下面举例说明。
1.3. 创建者模式举例
1.3.1. 定义合法参数
public enum Color {
RED, BLACK
}
public enum Model {
NORMAL, MAX
}
public enum CameraNum {
TWO, THREE
}
public enum MemorySize {
G128, G256, G512
}
复制代码
1.3.2. IPhone类
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;
}
// 1.独立的创建者,职责更清晰。2. 作为内部类,功能更内聚 3.不需要访问IPhone的成员变量-【静态】内部类
public static final class Builder {
private Color color;
private Model model;
private CameraNum cameraNum;
private MemorySize memorySize;
public Builder setColor(Color color) {
this.color = color;
return this;
}
public Builder setModel(Model model) {
this.model = model;
return this;
}
public Builder setCameraNum(CameraNum cameraNum) {
this.cameraNum = cameraNum;
return this;
}
public Builder setMemorySize(MemorySize memorySize) {
this.memorySize = memorySize;
return this;
}
public IPhone build() {
IPhone iPhone = new IPhone(this.color, 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();
}
}
复制代码
1.3.3. 测试类
public class BuilderDemo {
public static void main(String[] args) {
IPhone.Builder builder = new IPhone.Builder();
// 红色
IPhone redPhone = builder.setColor(Color.RED)
.setCameraNum(CameraNum.THREE)
.setMemorySize(MemorySize.G512)
.setModel(Model.MAX).build();
// 黑色
IPhone blackPhone = builder.setColor(Color.BLACK)
.setCameraNum(CameraNum.THREE)
.setMemorySize(MemorySize.G128)
.setModel(Model.NORMAL).build();
System.out.println(redPhone);
System.out.println(blackPhone);
}
}
复制代码
输出结果:
color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=THREE, memorySize=G128
复制代码
至此,创建者模式完成,一般的创建者设计模式举例,包括大厂的代码(例如Android中的JobInfo.Builder)也就到此为止了,但不知细心的读者是否发现了例子中存在的问题?
例子中的错误为:创建了一个错误的黑色IPhone,按照前面表格说明,并没有3个摄像头的黑色IPhone,但却创建了一个这样的黑色IPhone。
这种错误的解决方式有:
- 运行时抛出异常,这是最差的方式。
- 编译时异常,在这个场景无法做到。
- 增加API说明,让开发者参考文档避免错误。
- 通过不同的Builder创建对象实例,在编码时避免错误。
下面,我们就通过第4种方式在早期避免犯错。
1.4. 创建者模式优化,编码避免创建错误
1.4.1. 新增适用红色IPhone的参数定义
// 用于红色IPhone,限定内存范围
public enum RedMemorySize {
G256 {
@Override public MemorySize getMemorySize() {
return MemorySize.G256;
}
},
G512 {
@Override public MemorySize getMemorySize() {
return MemorySize.G512;
}
};
public abstract MemorySize getMemorySize();
}
复制代码
1.4.2. 定义不同的Builder创建不同的Iphone
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();
}
}
复制代码
1.4.3. 测试代码
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);
}
}
复制代码
输出:
color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128
复制代码
可以看到,这时创建的黑色IPhone是正确的。
1.5. 创建者模式经典范式
至此,我们可以总结出创建者模式的经典范式:
- 创建者和要创建对象分离,这样可解耦,职责更清晰。
- 创建者可作为创建对象的内部类(通常是静态内部类),这样更内聚,使得很容易找到创建者。
- 由于不同的条件/参数组合创建出的对象实例有不同行为,错误组合可能导致错误结果时,可以通过不同的Builder来创建对象,在编码时就避免犯错。
- 通过链式创建方式使得代码更简洁。
2. 总结
- 每个设计模式总有不能被替代的用途,如果发现能互相替代时,就用最简单的那种。
- 尽早避免编码犯错能使代码更健壮,纠错成本更低,所以应尽可能的在前期避免错误发生。
end.