在日常开发中,时常会遇到一个类中包含了很多成员变量的情况,并且这些成员变量有些是必须的,有些则是可选域。
那么对于这样的类,应该采用哪种方法或者构造器来编写呢?
假设 我们需要创建一个手机类,而手机含有很多必须属性,也有很多可选的属性。例如下面这个类。
public class Phone {
// 品牌
private String brand;
// 型号
private String model;
// 上市时间
private String marketDate;
// 运存
private int ram;
// 内存
private int rom;
// 价格
private int price;
// 电池容量
private int batteryCapacity;
// 颜色
private String colour;
// 长 mm
private double length;
// 宽 mm
private double width;
// 高 mm
private double height;
// 重量 g
private int weight;
// 屏幕尺寸 inches
private double screenSize;
// 处理器型号
private String cpu;
//===========================以下为可选
// 扬声器个数
private int speakerNum;
// 前置摄像头个数
private int preCameraNum;
// 后置摄像头个数
private int backCameraNum;
// 防水功能
private boolean waterproof;
// 3.5mm耳机孔
private boolean headsetHole;
// NFC功能
private boolean nfc;
// 红外功能
private boolean infrared;
// 无线充电
private boolean wirelessCharge;
// 反向无线充电
private boolean reWirelessCharge;
}
程序员一贯采用重叠构造器(telescoping constructor)模式,在这种模式下,你提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推。最后一个构造器拥有全部参数。
//我们需要创建很多构造器,以便调用者可以直接创建出可以使用的实例,所以有了如下构造器
//构造器1
public Phone(String brand, String model, String marketDate, int ram, int rom, int price, int batteryCapacity,
String colour, double length, double width, double height, int weight, double screenSize, String cpu,
int speakerNum, int preCameraNum, int backCameraNum, boolean waterproof, boolean headsetHole, boolean nfc,
boolean infrared, boolean wirelessCharge, boolean reWirelessCharge) {
super();
this.brand = brand;
this.model = model;
this.marketDate = marketDate;
this.ram = ram;
this.rom = rom;
this.price = price;
this.batteryCapacity = batteryCapacity;
this.colour = colour;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.screenSize = screenSize;
this.cpu = cpu;
this.speakerNum = speakerNum;
this.preCameraNum = preCameraNum;
this.backCameraNum = backCameraNum;
this.waterproof = waterproof;
this.headsetHole = headsetHole;
this.nfc = nfc;
this.infrared = infrared;
this.wirelessCharge = wirelessCharge;
this.reWirelessCharge = reWirelessCharge;
}
//构造器2
public Phone(String brand, String model, String marketDate, int ram, int rom, int price, int batteryCapacity,
String colour, double length, double width, double height, int weight, double screenSize, String cpu,
int speakerNum, int preCameraNum, int backCameraNum, boolean waterproof, boolean headsetHole, boolean nfc,
boolean infrared) {
super();
this.brand = brand;
this.model = model;
this.marketDate = marketDate;
this.ram = ram;
this.rom = rom;
this.price = price;
this.batteryCapacity = batteryCapacity;
this.colour = colour;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.screenSize = screenSize;
this.cpu = cpu;
this.speakerNum = speakerNum;
this.preCameraNum = preCameraNum;
this.backCameraNum = backCameraNum;
this.waterproof = waterproof;
this.headsetHole = headsetHole;
this.nfc = nfc;
this.infrared = infrared;
}
//构造器3
public Phone(String brand, String model, String marketDate, int ram, int rom, int price, int batteryCapacity,
String colour, double length, double width, double height, int weight, double screenSize, String cpu,
int speakerNum, int preCameraNum, int backCameraNum, boolean waterproof, boolean headsetHole) {
super();
this.brand = brand;
this.model = model;
this.marketDate = marketDate;
this.ram = ram;
this.rom = rom;
this.price = price;
this.batteryCapacity = batteryCapacity;
this.colour = colour;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.screenSize = screenSize;
this.cpu = cpu;
this.speakerNum = speakerNum;
this.preCameraNum = preCameraNum;
this.backCameraNum = backCameraNum;
this.waterproof = waterproof;
this.headsetHole = headsetHole;
}
//构造器4
public Phone(String brand, String model, String marketDate, int ram, int rom, int price, int batteryCapacity,
String colour, double length, double width, double height, int weight, double screenSize, String cpu,
int speakerNum, int preCameraNum, int backCameraNum, boolean waterproof) {
super();
this.brand = brand;
this.model = model;
this.marketDate = marketDate;
this.ram = ram;
this.rom = rom;
this.price = price;
this.batteryCapacity = batteryCapacity;
this.colour = colour;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.screenSize = screenSize;
this.cpu = cpu;
this.speakerNum = speakerNum;
this.preCameraNum = preCameraNum;
this.backCameraNum = backCameraNum;
this.waterproof = waterproof;
}
当你需要创建实例的时候,就要利用参数列表最短的构造器,但是该列表包含了要设置的所有参数。
Phone phone = new Phone(
"华为", "p40 pro", "2020年3月", 8,256,6488, 4100, "冰霜银",
72.6, 8.95, 158.2, 209, 6.58, "麒麟990 5G", 1,
2, 4, true, false, true,
true, true, true);
当有多个构造器时,调用者很容易晕头转向。
这个构造器通常会需要许多你本不想设置的参数,但是你还是不得不为他们传递值。如果仅仅是几个参数还不算太糟糕,问题是随着参数数目的增加,它很快会失去控制。
总结
重叠构造器模式可行,但是党有许多参数的时候,客户端代码会很难编写,并且仍然较难阅读。如果读者想知道哪些值是什么意思,必须很仔细的数着这些参数来一探究竟。一段长类型的相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了两个参数的顺序,编译器也不会报错,但是在程序运行间就会出现错误行为。
遇到许多构造参数的时候,还有第二种方法代替,即JavaBean模式,在这种模式下,调用一个无参构造器来创建对象,然后调用一个无参构造来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。
//我们为所有属性提供get、set方法,并提供无参构造器,通过set方法来进行赋值
public Phone() {
super();
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getMarketDate() {
return marketDate;
}
public void setMarketDate(String marketDate) {
this.marketDate = marketDate;
}
public int getRam() {
return ram;
}
public void setRam(int ram) {
this.ram = ram;
}
public int getRom() {
return rom;
}
public void setRom(int rom) {
this.rom = rom;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getBatteryCapacity() {
return batteryCapacity;
}
public void setBatteryCapacity(int batteryCapacity) {
this.batteryCapacity = batteryCapacity;
}
public String getColour() {
return colour;
}
public void setColour(String colour) {
this.colour = colour;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public double getScreenSize() {
return screenSize;
}
public void setScreenSize(double screenSize) {
this.screenSize = screenSize;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getSpeakerNum() {
return speakerNum;
}
public void setSpeakerNum(int speakerNum) {
this.speakerNum = speakerNum;
}
public int getPreCameraNum() {
return preCameraNum;
}
public void setPreCameraNum(int preCameraNum) {
this.preCameraNum = preCameraNum;
}
public int getBackCameraNum() {
return backCameraNum;
}
public void setBackCameraNum(int backCameraNum) {
this.backCameraNum = backCameraNum;
}
public boolean isWaterproof() {
return waterproof;
}
public void setWaterproof(boolean waterproof) {
this.waterproof = waterproof;
}
public boolean isHeadsetHole() {
return headsetHole;
}
public void setHeadsetHole(boolean headsetHole) {
this.headsetHole = headsetHole;
}
public boolean isNfc() {
return nfc;
}
public void setNfc(boolean nfc) {
this.nfc = nfc;
}
public boolean isInfrared() {
return infrared;
}
public void setInfrared(boolean infrared) {
this.infrared = infrared;
}
public boolean isWirelessCharge() {
return wirelessCharge;
}
public void setWirelessCharge(boolean wirelessCharge) {
this.wirelessCharge = wirelessCharge;
}
public boolean isReWirelessCharge() {
return reWirelessCharge;
}
public void setReWirelessCharge(boolean reWirelessCharge) {
this.reWirelessCharge = reWirelessCharge;
}
这种方式弥补了重叠构造器的不足。说得明白一点就是读起来容易,用起来也容易。
Phone phone = new Phone();
phone.setBrand("华为");
phone.setModel("p40 pro");
phone.setMarketDate("2020年3月");
phone.setRam(4);
phone.setRom(256);
phone.setPrice(6488);
phone.setBatteryCapacity(4100);
phone.setColour("冰霜银");
phone.setLength(158.2);
phone.setWidth(72.6);
phone.setHeight(8.95);
phone.setWeight(209);
phone.setCpu("麒麟990 5G");
phone.setSpeakerNum(1);
phone.setPreCameraNum(2);
phone.setBackCameraNum(4);
phone.setWaterproof(true);
phone.setHeadsetHole(false);
phone.setNfc(true);
phone.setInfrared(true);
phone.setWirelessCharge(true);
phone.setReWirelessCharge(true);
遗憾的是,JavaBean 模式自身有着很严重的缺点。
幸运的是,还有第三种替代方法,既能保证像重叠构造器那样的安全性,也能保证像JavaBean模式那么好的可读性,这就是Build模式。
不直接生成想要的对象,而是让客户端利用所有构造的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。
@SuppressWarnings("unused")
public class Phone {
// 品牌
private final String brand;
// 型号
private final String model;
// 上市时间
private final String marketDate;
// 运存
private final int ram;
// 内存
private final int rom;
// 价格
private final int price;
// 电池容量
private final int batteryCapacity;
// 颜色
private final String colour;
// 长 mm
private final double length;
// 宽 mm
private final double width;
// 高 mm
private final double height;
// 重量 g
private final int weight;
// 屏幕尺寸 inches
private final double screenSize;
// 处理器型号
private final String cpu;
// 扬声器个数
private final int speakerNum;
// 前置摄像头个数
private final int preCameraNum;
// 后置摄像头个数
private final int backCameraNum;
// 防水功能
private final boolean waterproof;
// 3.5mm耳机孔
private final boolean headsetHole;
// NFC功能
private final boolean nfc;
// 红外功能
private final boolean infrared;
// 无线充电
private final boolean wirelessCharge;
// 反向无线充电
private final boolean reWirelessCharge;
public static class Builder {
// ==================必须参数==================
// 品牌
private final String brand;
// 型号
private final String model;
// 上市时间
private final String marketDate;
// 运存
private final int ram;
// 内存
private final int rom;
// 价格
private final int price;
// 电池容量
private final int batteryCapacity;
// 颜色
private final String colour;
// 长 mm
private final double length;
// 宽 mm
private final double width;
// 高 mm
private final double height;
// 重量 g
private final int weight;
// 屏幕尺寸 inches
private final double screenSize;
// 处理器型号
private final String cpu;
// ==================不必须参数,给予默认值==================
// 扬声器个数
private int speakerNum = 1;
// 前置摄像头个数
private int preCameraNum = 0;
// 后置摄像头个数
private int backCameraNum = 1;
// 防水功能
private boolean waterproof = false;
// 3.5mm耳机孔
private boolean headsetHole = false;
// NFC功能
private boolean nfc = false;
// 红外功能
private boolean infrared = false;
// 无线充电
private boolean wirelessCharge = false;
// 反向无线充电
private boolean reWirelessCharge = false;
public Builder(String brand, String model, String marketDate, int ram, int rom, int price, int batteryCapacity,
String colour, double length, double width, double height, int weight, double screenSize, String cpu) {
super();
this.brand = brand;
this.model = model;
this.marketDate = marketDate;
this.ram = ram;
this.rom = rom;
this.price = price;
this.batteryCapacity = batteryCapacity;
this.colour = colour;
this.length = length;
this.width = width;
this.height = height;
this.weight = weight;
this.screenSize = screenSize;
this.cpu = cpu;
}
public Builder speakerNum(int val) {
speakerNum = val;
return this;
}
public Builder preCameraNum(int val) {
preCameraNum = val;
return this;
}
public Builder backCameraNum(int val) {
backCameraNum = val;
return this;
}
public Builder waterproof(boolean val) {
waterproof = val;
return this;
}
public Builder headsetHole(boolean val) {
headsetHole = val;
return this;
}
public Builder nfc(boolean val) {
nfc = val;
return this;
}
public Builder infrared(boolean val) {
infrared = val;
return this;
}
public Builder wirelessCharge(boolean val) {
wirelessCharge = val;
return this;
}
public Builder reWirelessCharge(boolean val) {
reWirelessCharge = val;
return this;
}
public Phone builder() {
return new Phone(this);
}
}
private Phone(Builder builder) {
brand = builder.brand;
model = builder.model;
marketDate = builder.marketDate;
ram = builder.ram;
rom = builder.rom;
price = builder.price;
batteryCapacity = builder.batteryCapacity;
colour = builder.colour;
length = builder.length;
width = builder.width;
height = builder.height;
weight = builder.weight;
screenSize = builder.screenSize;
cpu = builder.cpu;
speakerNum = builder.speakerNum;
preCameraNum = builder.preCameraNum;
backCameraNum = builder.backCameraNum;
waterproof = builder.waterproof;
headsetHole = builder.headsetHole;
nfc = builder.nfc;
infrared = builder.infrared;
wirelessCharge = builder.wirelessCharge;
reWirelessCharge = builder.reWirelessCharge;
}
}
客户端代码,这样的客户端代码更容易编写,更为重要的是,易于阅读。builder模式模拟了具体名的可选参数,就像Ada和Python中的一样。
Phone phone = new Phone.Builder("华为", "p40 pro", "2020年3月", 8, 256, 6488, 4100, "冰霜银", 158.2, 72.6, 8.95, 209,
6.58, "麒麟990 5G").speakerNum(1).preCameraNum(2).backCameraNum(4).nfc(true).waterproof(true)
.headsetHole(false).infrared(true).wirelessCharge(true).reWirelessCharge(true).builder();
与构造器相比,builder的微略优势在于,builder可以有多个可变的(varargs)参数。构造器就像方法一样,只能有一个可变参数。因为builder利用单独的方法来设置每个参数,你想要多少个可变参数,他就可以有多少个,直到每个setter都有一个可变参数。
Builder模式也有自身的不足,为了创建对象,必须先创建他的构建器。虽然创建构建器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就问题了。
Builder 模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如4个或更多参数,但请记住,将来你可能需要添加参数。如果一开始就是用构造器或者静态工厂,等到类需要多个参数时才添加构建器,就会无法控制,那些过时的构造器或者静态工厂显得十分不协调。因此,通常最好一开始就使用构建器。
何时使用 Builer 模式