1.1 前言
近来,在学习缓存的过程中,发现Guava和Caffeine等本地缓存都是使用建造者模式来创建对象,因此针对建造者模式进行学习和实现。
1.2 目标
- 理解建造者模式与折叠构造函数模式以及JavaBean模式创建对象的区别
- 如何使用链式调用在没有调用某种参数的情况下使用默认构造参数
1.3 定义
建造者模式是一种创建型模式,它是将复杂对象的创建与其表示分离,使同样的创建过程可以创建不同的表示。
1.4 使用场景
当一个类的构造函数参数超过4个,而这些参数中某些参数是可选地,考虑使用构造者模式。
1.5 解决的问题
下面以电脑(computer)的部分组件实现一个建造者模式,其中属性cpu和ram是必选的,属性usbCount、keyboard、display是可选的。
1.5.1 相较于其他两种方法的区别
(1)构造函数
当Computer中的一些参数为必选属性,而另一些参数为可选属性时,可通过折叠构造函数来创建对象
public class Computer {
public Computer(String cpu, String ram) {
this(cpu, ram);
}
public Computer(String cpu, String ram, int usbCount) {
this(cpu, ram, usbCount);
}
public Computer(String cpu, String ram, int usbCount, String keyboard) {
this(cpu, ram, usbCount, keyboard);
}
public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
this.cpu = cpu;
this.ram = ram;
this.usbCount = usbCount;
this.keyboard = keyboard;
this.display = display;
}
}
(2)Java Bean模式
public class Computer {
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getRam() {
return ram;
}
public void setRam(String ram) {
this.ram = ram;
}
public int getUsbCount() {
return usbCount;
}
public void setUsbCount(String usbCount) {
this.usbCount= usbCount;
}
public String getKeyboard() {
return keyboead;
}
public void setUsbCount(String keyboard) {
this.keyboard= keyboard;
}
public String getDisplay() {
return display;
}
public void setDisplay(String display) {
this.display= display;
}
}
(3)它们的弊端
第一种使用和阅读复杂,第二种构建过程中对象的状态容易发生变化,造成错误。
1.5.2 默认传参
在Guava和Caffeine创建对象中,它们的源码中都有某些属性的初始值。当调用者没有为某个属性赋值(null)时则使用源码中定义的初始值。这部分的功能可以在构造函数中判断用户是否传入值进而决定使用用户赋予的值还是默认初始化值。
1.6 实现
1.6.1 静态内部类
静态内部类是在Java中定义的一种特殊类型的内部类,它使用static
关键字进行修饰。与普通的非静态内部类相比,静态内部类有以下几个特点和使用场景:
特点:
- 独立于外部类实例:静态内部类不需要依赖外部类的实例就可以被创建和使用。这一点与非静态内部类不同,非静态内部类必须依附于一个外部类的实例。
- 访问权限:静态内部类只能访问外部类的静态成员和方法,而不能直接访问外部类的非静态成员或方法,因为它不依赖于外部类的实例。
- 加载时机:静态内部类是在首次被访问时加载,而不是随着外部类的加载而加载。这意味着它有助于实现延迟加载,提高性能。
- 内存占用:静态内部类不持有对外部类实例的引用,这减少了内存占用。
- 命名空间和封装性:静态内部类提供了一个额外的命名空间,有助于组织代码和避免命名冲突,同时也增强了代码的封装性。
使用场景:
- 单例模式实现:静态内部类可以用来实现线程安全的懒汉式单例模式,因为静态内部类只会在第一次被访问时初始化,确保了实例的唯一性且延迟了实例的创建。
- 工具类和常量类:静态内部类常被用来定义工具方法或存储常量,这样可以保持外部类的纯净,并且便于管理这些工具方法或常量。
- 封装和模块化(在本文中的作用):将某些类作为另一个类的内部类,尤其是当它们之间存在逻辑上的关联但又希望保持独立性时,可以增强代码的组织性和可读性。
- 优化加载和性能:对于那些不经常使用或者初始化成本较高的类,可以考虑放在静态内部类中,以达到按需加载的目的。
1.6.2 建造者模式实现流程
- 创建一些静态常量来初始化Computer(外部类)的可选参数;
- 在Computer(外部类)中创建一个静态内部类Builder,并将Computer(外部类)中的所有参数复制一份到Builder(静态内部类)中;
- 在Computer(外部类)中创建一个private类型的构造方法,参数为Builder(静态内部类)类型;
- 在Builder(静态内部类)中创建一个public类型的构造方法,参数为Computer(外部类)的必选参数;
- 在Builder(静态内部类)中使用set函数为Computer(外部类)的可选参数赋值,返回类型为Builder(静态内部类)类型;
- 在Builder(静态内部类)中创建一个build()方法,构建Computer(外部类)的实例并返回。
1.6.3 代码实现
package com.ku.test.cache.designpattern;
public class Computer {
/**
* 实现流程
* 1.在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
* 2.在Computer中创建一个private的构造函数,参数为Builder类型
* 3.在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
* 4.在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
* 5.在Builder中创建一个build()方法,在其中构建Computer的实例并返回
*/
private final String cpu;
private final String ram;
private final String usbCount;
private final String keyboard;
private final String display;
private static final String initUsbCount = "华为";
private static final String initKeyboard = "裸机";
private static final String initDisplay = "2";
/**
* 为了实现在不调用setDisplay()方法时也能默认使用initDisplay来设置display,您可以在Computer类的构造器中进行判断。
* 如果从Builder传递过来的display为null,则将其设置为默认值。
* @param builder
*/
public Computer(Builder builder){
this.cpu = builder.cpu;//必须参数
this.ram = builder.ram;//必须参数
//可选参数
this.usbCount = builder.usbCount != null ? builder.usbCount : initUsbCount;
this.keyboard = builder.keyboard != null ? builder.keyboard : initKeyboard;
this.display = builder.display != null ? builder.display : initDisplay;
}
private static class Builder{
private String cpu;
private String ram;
private String usbCount;
private String keyboard;
private String display;
public Builder(String cpu, String ram){
this.cpu = cpu;
this.ram = ram;
}
public String getUsbCount() {
return usbCount;
}
public Builder setUsbCount(String usbCount) {
this.usbCount = usbCount;
return this;
}
public String getKeyboard() {
return keyboard;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public String getDisplay() {
return display;
}
public Builder setDisplay(String display) {
this.display = (display != null) ? display : initDisplay;
return this;
}
public Computer build(){
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount='" + usbCount + '\'' +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
//使用
public static void main(String[] args) {
Computer computer = new Computer.Builder("因特尔", "SSD")
.setUsbCount("USB3")
.setKeyboard("裸机")
.setDisplay("3")
.build();
System.out.println(computer.toString());
}
}
建造者模式实现
1.6.4 结果
(1)当不传入display属性时会使用默认初始化display
(2)当传入display属性时会使用用户赋予的值
1.7 小结
建造者模式通过匿名内部类(关联外部类),构造器(必选参数,链式调用(初始化值))以及set方法(可选参数)组合了复杂对象的创建,提高了创建复杂对象的性能。
参考链接
秒懂设计模式之建造者模式(Builder pattern) - 知乎 (zhihu.com)