目录
一、为什么需要建造者模式?
- 什么时候使用建造者模式?
1、场景1
- 有时候,一个类中有很多字段,或者有些字段也是引用类型。这时候,当我们实例化这个类时(建造这个类的对象时),会比较麻烦:
- 方案:提供入参巨多的构造器
public class ComplexClass {
private String name;
private String email;
private Integer age;
private Pet pet;
...
public ComplexClass(String name, String email, ...) {
}
}
2、场景2
- 有各种各样的房子,它们有一些共同的属性,也有一些独特的属性。
- 一种设计是:抽象父类 + 多个子类。
- 这可能导致子类爆炸,明明也是房子对象,却不得不搞一个新的房子类。
3、解决上述场景的办法:建造者模式
- 示例:
public class House {
private List<Wall> walls;
private List<Door> doors;
...
}
public interface IHouseBuilder {
void buildWalls(List<Wall> walls);
void buildDoors(List<Door> doors);
...
House getProduct();
}
public class HouseWithGarageBuilder implements IHouseBuilder {
private House house;
public HouseWithGarageBuilder() {
house = new House();
}
public buildWalls(List<Wall> walls) {
this.walls = walls;
}
public buildDoors(List<Door> doors) {
this.doors = doors;
}
...
public House getProduct() {
return house;
}
}
public class HouseDirector {
private IHouseBuilder builder;
public HouseDirector(IHouseBuilder builder) {
this.builder = builder;
}
public House constructHouse() {
builder.buildWalls(...);
builder.buildDoors(...);
return builder.getProduct();
}
}
- 本质:分步骤建造对象。
二、练习
1、题目描述 【来源】
小明家新开了一家自行车工厂,用于使用自行车配件(车架 frame 和车轮 tires )进行组装定制不同的自行车,包括山地车和公路车。
山地车使用的是Aluminum Frame(铝制车架)和 Knobby Tires(可抓地轮胎),公路车使用的是 Carbon Frame (碳车架)和 Slim Tries。
现在它收到了一笔订单,要求定制一批自行车,请你使用【建造者模式】告诉小明这笔订单需要使用那些自行车配置吧。
2、输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示订单的数量。
接下来的 N 行,每行输入一个字符串,字符串表示客户的自行车需求。
字符串可以包含关键词 “mountain” 或 “road”,表示客户需要山地自行车或公路自行车。
3、输出描述
对于每笔订单,输出该订单定制的自行车配置。
4、输入示例
3
mountain
road
mountain
5、输出示例
Aluminum Frame Knobby Tires
Carbon Frame Slim Tires
Aluminum Frame Knobby Tires
6、参考
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bike {
private Frame frame;
private Tires tires;
@Override
public String toString() {
return frame.getType() + " " + tires.getType();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Frame {
private String type;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tires {
private String type;
}
public interface IBikeBuilder {
void buildFrame();
void buildTires();
Bike getProduct();
}
public class MountainBikeBuilder implements IBikeBuilder {
private Bike bike;
public MountainBikeBuilder() {
bike = new Bike();
}
@Override
public void buildFrame() {
bike.setFrame(new Frame("Aluminum Frame"));
}
@Override
public void buildTires() {
bike.setTires(new Tires("Knobby Tires"));
}
@Override
public Bike getProduct() {
return bike;
}
}
public class RoadBikeBuilder implements IBikeBuilder {
private Bike bike;
public RoadBikeBuilder() {
bike = new Bike();
}
@Override
public void buildFrame() {
bike.setFrame(new Frame("Carbon Frame"));
}
@Override
public void buildTires() {
bike.setTires(new Tires("Slim Tries"));
}
@Override
public Bike getProduct() {
return bike;
}
}
public class BikeDirector {
public Bike constructBike(IBikeBuilder bikeBuilder) {
bikeBuilder.buildFrame();
bikeBuilder.buildTires();
return bikeBuilder.getProduct();
}
}
public class Main {
public static void main(String[] args) {
BikeDirector bikeDirector = new BikeDirector();
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine();
for (int i = 0; i < n; i++) {
String type = scanner.nextLine();
IBikeBuilder bikeBuilder;
if ("mountain".equals(type)) {
bikeBuilder = new MountainBikeBuilder();
} else if ("road".equals(type)) {
bikeBuilder= new RoadBikeBuilder();
} else {
throw new RuntimeException("type error");
}
Bike bike = bikeDirector.constructBike(bikeBuilder);
System.out.println(bike.toString());
}
}
}
三、思考
- 现实情况下,我还没这么用过建造者模式,因为按照上面的写法,还是很费劲的。
1、一般常用的是lombok库的@Builder注解。
- 示例:
@Builder
@ToString
public class User {
private String name;
private Integer age;
private String email;
private String gender;
}
public class Main {
public static void main(String[] args) {
User user = User.builder()
.name("Forrest")
.age(20)
.email("forrest@qq.com")
.gender("male")
.build();
System.out.println(user);
}
}
这种建造对象的写法看起来还是很舒服的。
- 编译后的User.class
public class User {
private String name;
private Integer age;
private String email;
private String gender;
User(String name, Integer age, String email, String gender) {
this.name = name;
this.age = age;
this.email = email;
this.gender = gender;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public String toString() {
return "User(name=" + this.name + ", age=" + this.age + ", email=" + this.email + ", gender=" + this.gender + ")";
}
public static class UserBuilder {
private String name;
private Integer age;
private String email;
private String gender;
UserBuilder() {
}
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder age(Integer age) {
this.age = age;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public UserBuilder gender(String gender) {
this.gender = gender;
return this;
}
public User build() {
return new User(this.name, this.age, this.email, this.gender);
}
public String toString() {
return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ", email=" + this.email + ", gender=" + this.gender + ")";
}
}
}
- 可以清楚地看到,User中生成了一个内部类UserBuilder,来分步骤设置字段。
2、但@Builder注解有很多坑点
- 显而易见的一点:
@Builder
@ToString
public class User {
private String name = "Forrest";
private Integer age;
private String email;
private String gender;
}
public class Main {
public static void main(String[] args) {
User user = User.builder()
.age(20)
.email("forrest@qq.com")
.gender("male")
.build();
System.out.println(user);
}
}
User(name=null, age=20, email=forrest@qq.com, gender=male)
- 默认值就这样丢了…
@Builder
@ToString
@AllArgsConstructor
public class User {
private String name = "Forrest";
private Integer age;
private String email;
private String gender;
public User(Integer age, String email, String gender) {
this.age = age;
this.email = email;
this.gender = gender;
}
}
public class Main {
public static void main(String[] args) {
User user = new User(20, "forrest@qq.com", "male");
System.out.println(user);
}
}
User(name=Forrest, age=20, email=forrest@qq.com, gender=male)
- 不得不加上@AllArgsConstructor,因为@Builder生成的UserBuilder中用到了全参构造器。
3、不如用@Accessors(chain = true)
@Data
@Accessors(chain = true)
public class User {
private String name = "Forrest";
private Integer age;
private String email;
private String gender;
}
public class Main {
public static void main(String[] args) {
User user = new User()
.setAge(18)
.setEmail("forrest@qq.com")
.setGender("male");
System.out.println(user);
}
}
User(name=Forrest, age=18, email=forrest@qq.com, gender=male)