目录
- 一、为什么需要建造者模式?
- 1、场景1
- 2、场景2
- 3、解决上述场景的办法:建造者模式
- 二、练习
- 1、题目描述 【[来源](https://kamacoder.com/problempage.php?pid=1084)】
- 2、输入描述
- 3、输出描述
- 4、输入示例
- 5、输出示例
- 6、参考
- 三、思考
- 1、一般常用的是lombok库的@Builder注解。
- 2、但@Builder注解有很多坑点
- 3、不如用@Accessors(chain = true)
一、为什么需要建造者模式?
- 什么时候使用建造者模式?
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;@Overridepublic 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();}@Overridepublic void buildFrame() {bike.setFrame(new Frame("Aluminum Frame"));}@Overridepublic void buildTires() {bike.setTires(new Tires("Knobby Tires"));}@Overridepublic Bike getProduct() {return bike;}
}public class RoadBikeBuilder implements IBikeBuilder {private Bike bike;public RoadBikeBuilder() {bike = new Bike();}@Overridepublic void buildFrame() {bike.setFrame(new Frame("Carbon Frame"));}@Overridepublic void buildTires() {bike.setTires(new Tires("Slim Tries"));}@Overridepublic 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)