意图
生成器模式(Builder)是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。
解决方案
生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。
生成器模式让你能够分步骤创建复杂对象。 生成器不允许其他对象访问正在创建中的产品。
每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。
当你需要创建不同形式的产品时,其中的一些构造步骤可能需要不同的实现。
在这种情况下,你可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。然后你就可以在创建过程中使用这些生成器(例如按顺序调用多个构造步骤)来生成不同类型的对象。
你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。
生成器模式结构
生成器(Builder)接口声明在所有类型生成器中通用的产品构造步骤。
具体生成器(Concrete Builders)提供构造过程的不同实现。具体生成器也可以构造不遵循通用接口的产品。
产品(Products)是最终生成的对象。由不同生成器构造的产品无需属于同一类层次结构或接口。
主管(Director)类定义调用构造步骤的顺序,这样你就可以创建和复用特定的产品配置。
客户端(Client)必须将某个生成器对象与主管类关联。一般情况下,你只需通过主管类构造函数的参数进行一次性关联即可。此后主管类就能使用生成器对象完成后续所有的构造任务。但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。在这种情况下,你在使用主管类生产产品时每次都可以使用不同的生成器。
生成器模式适合应用场景
- 使用生成器模式可避免 “重叠构造函数(telescopic constructor)”的出现。
假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便;因此,你需要重载这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。
生成器模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。
- 当你希望使用代码创建不同形式的产品(例如石头或木头房屋)时,可使用生成器模式。
如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用生成器模式。
基本生成器接口中定义了所有可能的制造步骤,具体生成器将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。
- 使用生成器构造组合树或其他复杂对象。
生成器模式让你能分步骤构造产品。你可以延迟执行某些步骤而不会影响最终产品。你甚至可以递归调用这些步骤,这在创建对象树时非常方便。
生成器在执行制造步骤时,不能对外发布未完成的产品。这可以避免客户端代码获取到不完整结果对象的情况。
生成器模式优缺点
优点:
- 你可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
- 生成不同形式的产品时,你可以复用相同的制造代码。
- 单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。
缺点:
- 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
与其他模式的关系
-
在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
-
生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。
在 Java 中使用模式
使用示例:生成器模式是 Java 世界中的一个著名模式。当你需要创建一个可能有许多配置选项的对象时,该模式会特别有用。
生成器在 Java 核心程序库中得到了广泛的应用:
java.lang.StringBuilder#append()
(非同步
)java.lang.StringBuffer#append()
(同步
)java.nio.ByteBuffer#put()
(还有CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
和DoubleBuffer
)javax.swing.GroupLayout.Group#addComponent()
java.lang.Appendable
的所有实现
示例代码
产品(Products) 和 生成器(Builder)
Builder作为Pet的内部静态类,同时限制了Pet的构造器为私有的,因此客户端只能通过Builder创建Pet对象。
public class Pet {
private final Animal animal;
private final String petName;
private final String ownerName;
private final String address;
private final String telephone;
private final LocalDate dateOfBirth; // optional
private final String emailAddress; //optional
private Pet(
final Animal animal,
final String petName,
final String ownerName,
final String address,
final String telephone,
final LocalDate dateOfBirth,
final String emailAddress) {
this.animal = animal;
this.petName = petName;
this.ownerName = ownerName;
this.address = address;
this.telephone = telephone;
this.dateOfBirth = dateOfBirth;
this.emailAddress = emailAddress;
}
@Override
public String toString() {
return "Pet{" +
"animal=" + animal +
", petName='" + petName + '\'' +
", ownerName='" + ownerName + '\'' +
'}';
}
public static class Builder {
private Animal animal;
private String petName;
private String ownerName;
private String address;
private String telephone;
private LocalDate dateOfBirth;
private String emailAddress;
public Builder withAnimal(final Animal animal) {
this.animal = animal;
return this;
}
public Builder withPetName(final String petName) {
this.petName = petName;
return this;
}
public Builder withOwnerName(final String ownerName) {
this.ownerName = ownerName;
return this;
}
public Builder withAddress(final String address) {
this.address = address;
return this;
}
public Builder withTelephone(final String telephone) {
this.telephone = telephone;
return this;
}
public Builder withDateOfBirth(final LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
return this;
}
public Builder withEmailAddress(final String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
public Pet build() {
if (animal == null ||
petName == null ||
ownerName == null ||
address == null ||
telephone == null) {
throw new IllegalStateException("Cannot create Pet");
}
return new Pet(animal, petName, ownerName, address, telephone, dateOfBirth, emailAddress);
}
}
}
public enum Animal {
CAT,
DOG
}
客户端(Client)
Builder支持了链式调用。
public class Client {
public static void main(String[] args) {
Pet.Builder builder = new Builder()
.withAnimal(Animal.CAT)
.withPetName("Kate")
.withOwnerName("Harold")
.withAddress("Chengdu, Sichuan, China")
.withTelephone("1151130")
.withDateOfBirth(LocalDate.of(2018, 6, 23));
Pet pet = builder.build();
System.out.println(pet);
}
}
使用主管的示例代码
生成器(Builder)
public interface Builder {
void setCarType(CarType type);
void setSeats(int seats);
void setEngine(Engine engine);
void setTransmission(Transmission transmission);
void setTripComputer(TripComputer tripComputer);
void setGPSNavigator(GPSNavigator gpsNavigator);
}
具体生成器(Concrete Builders)
public class CarBuilder implements Builder {
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;
public void setCarType(CarType type) {
this.type = type;
}
@Override
public void setSeats(int seats) {
this.seats = seats;
}
@Override
public void setEngine(Engine engine) {
this.engine = engine;
}
@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}
@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}
@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}
public Car getResult() {
return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}
public class CarManualBuilder implements Builder{
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;
@Override
public void setCarType(CarType type) {
this.type = type;
}
@Override
public void setSeats(int seats) {
this.seats = seats;
}
@Override
public void setEngine(Engine engine) {
this.engine = engine;
}
@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}
@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}
@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}
public Manual getResult() {
return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}
产品(Products)
public class Car {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;
private double fuel = 0;
public Car(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
if (this.tripComputer != null) {
this.tripComputer.setCar(this);
}
this.gpsNavigator = gpsNavigator;
}
public CarType getCarType() {
return carType;
}
public double getFuel() {
return fuel;
}
public void setFuel(double fuel) {
this.fuel = fuel;
}
public int getSeats() {
return seats;
}
public Engine getEngine() {
return engine;
}
public Transmission getTransmission() {
return transmission;
}
public TripComputer getTripComputer() {
return tripComputer;
}
public GPSNavigator getGpsNavigator() {
return gpsNavigator;
}
}
public class Manual {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;
public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
this.gpsNavigator = gpsNavigator;
}
public String print() {
String info = "";
info += "Type of car: " + carType + "\n";
info += "Count of seats: " + seats + "\n";
info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
info += "Transmission: " + transmission + "\n";
if (this.tripComputer != null) {
info += "Trip Computer: Functional" + "\n";
} else {
info += "Trip Computer: N/A" + "\n";
}
if (this.gpsNavigator != null) {
info += "GPS Navigator: Functional" + "\n";
} else {
info += "GPS Navigator: N/A" + "\n";
}
return info;
}
}
主管(Director)
public class Director {
public void constructSportsCar(Builder builder) {
builder.setCarType(CarType.SPORTS_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(3.0, 0));
builder.setTransmission(Transmission.SEMI_AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}
public void constructCityCar(Builder builder) {
builder.setCarType(CarType.CITY_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(1.2, 0));
builder.setTransmission(Transmission.AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}
public void constructSUV(Builder builder) {
builder.setCarType(CarType.SUV);
builder.setSeats(4);
builder.setEngine(new Engine(2.5, 0));
builder.setTransmission(Transmission.MANUAL);
builder.setGPSNavigator(new GPSNavigator());
}
}
客户端(Client)
public class Client {
public static void main(String[] args) {
Director director = new Director();
// Director gets the concrete builder object from the client
// (application code). That's because application knows better which
// builder to use to get a specific product.
CarBuilder builder = new CarBuilder();
director.constructSportsCar(builder);
// The final product is often retrieved from a builder object, since
// Director is not aware and not dependent on concrete builders and
// products.
Car car = builder.getResult();
System.out.println("Car built:\n" + car.getCarType());
CarManualBuilder manualBuilder = new CarManualBuilder();
// Director may know several building recipes.
director.constructSportsCar(manualBuilder);
Manual carManual = manualBuilder.getResult();
System.out.println("\nCar manual built:\n" + carManual.print());
}
}