建造者模式——不止提高代碼檔次
本文轉載自微信公眾號「JavaKeeper」,作者海星。轉載本文請聯系JavaKeeper公眾號。
簡介
Builder Pattern,中文翻譯為建造者模式或者構建者模式,也有人叫它生成器模式。
建造者模式是一種創建型設計模式, 使你能夠分步驟創建復雜對象。它允許用戶只通過指定復雜對象的類型和內容就可以構建它們,用戶不需要知道內部的具體構建細節。
定義:將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
hello world
程序員麼,先上個 hello world 熱熱身
- public class User {
- private Long id;
- private String name;
- private Integer age; //可選
- private String desc; //可選
- private User(Builder builder) {
- this.id = builder.id;
- this.name = builder.name;
- this.age = builder.age;
- this.desc = builder.desc;
- }
- public static Builder newBuilder(Long id, String name) {
- return new Builder(id, name);
- }
- public Long getId() {return id;}
- public String getName() {return name;}
- public Integer getAge() {return age;}
- public String getDesc() {return desc;}
- @Override
- public String toString() {
- return "Builder{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", age=" + age +
- ", desc='" + desc + '\'' +
- '}';
- }
- public static class Builder {
- private Long id;
- private String name;
- private Integer age;
- private String desc;
- private Builder(Long id, String name) {
- Assert.assertNotNull("標識不能為空",id);
- Assert.assertNotNull("名稱不能為空",name);
- this.id = id;
- this.name = name;
- }
- public Builder age(Integer age) {
- this.age = age;
- return this;
- }
- public Builder desc(String desc) {
- this.desc = desc;
- return this;
- }
- public User build() {
- return new User(this);
- }
- }
- public static void main(String[] args) {
- User user = User.newBuilder(1L, "starfish").age(22).desc("test").build();
- System.out.println(user.toString());
- }
- }
這樣的代碼有什么優缺點呢?
主要優點:
- 明確了必填參數和可選參數,在構造方法中進行驗證;
- 可以定義為不可變類,初始化后屬性字段值不可變更;
- 賦值代碼可讀性較好,明確知道哪個屬性字段對應哪個值;
- 支持鏈式方法調用,相比于調用 Setter 方法,代碼更簡潔。
主要缺點:
- 代碼量較大,多定義了一個 Builder 類,多定義了一套屬性字段,多實現了一套賦值方法;
- 運行效率低,需要先創建 Builder 實例,再賦值屬性字段,再創建目標實例,最后拷貝屬性字段。
當然,以上代碼,就可以通過 Lombok 的 @Builder 簡化代碼
如果我們就那么三三兩兩個參數,直接構造函數配合 set 方法就能搞定的,就不用套所謂的模式了。
高射炮打蚊子——不合算
假設有這樣一個復雜對象, 在對其進行構造時需要對諸多成員變量和嵌套對象進行繁復的初始化工作。這些初始化代碼通常深藏于一個包含眾多參數且讓人基本看不懂的構造函數中;甚至還有更糟糕的情況, 那就是這些代碼散落在客戶端代碼的多個位置。
這時候才是構造器模式上場的時候
上邊的例子,其實屬于簡化版的建造者模式,只是為了方便構建類中的各個參數,”正經“的和這個有點差別,更傾向于用同樣的構建過程分步創建不同的產品類。
我們接著扯~
結構
從 UML 圖上可以看到有 4 個不同的角色
- 抽象建造者(Builder):創建一個 Produc 對象的各個部件指定的接口/抽象類
- 具體建造者(ConcreteBuilder):實現接口,構建和裝配各個組件
- 指揮者/導演類(Director):構建一個使用 Builder 接口的對象。負責調用適當的建造者來組建產品,導演類一般不與產品類發生依賴關系,與導演類直接交互的是建造者類。
- 產品類(Product):一個具體的產品對象
demo
假設我是個汽車工廠,需求就是能造各種車(或者造電腦、造房子、做煎餅、生成不同文件TextBuilder、HTMLBuilder等等,都是一個道理)
1、生成器(Builder)接口聲明在所有類型生成器中通用的產品構造步驟
- public interface CarBuilder {
- void setCarType(CarType type);
- void setSeats(int seats);
- void setEngine(Engine engine);
- void setGPS(GPS gps);
- }
2、具體的生成器(Concrete Builders)提供構造過程的不同實現
- public class SportsCarBuilder implements CarBuilder {
- private CarType carType;
- private int seats;
- private Engine engine;
- private GPS gps;
- @Override
- public void setCarType(CarType type) {
- this.carType = type;
- }
- @Override
- public void setSeats(int seats) {
- this.seats = seats;
- }
- @Override
- public void setEngine(Engine engine) {
- this.engine = engine;
- }
- @Override
- public void setGPS(GPS gps) {
- this.gps = gps;
- }
- public Car getResult() {
- return new Car(carType, seats, engine, gps);
- }
- }
3、產品(Products)是最終生成的對象
- @Setter
- @Getter
- @ToString
- public class Car {
- private final CarType carType;
- private final int seats;
- private final Engine engine;
- private final GPS gps;
- private double fuel;
- public Car(CarType carType,int seats,Engine engine,GPS gps){
- this.carType = carType;
- this.seats = seats;
- this.engine = engine;
- this.gps = gps;
- }
- }
4、主管(Director)類定義調用構造步驟的順序,這樣就可以創建和復用特定的產品配置(Director 類的構造函數的參數是 CarBuilder,但實際上沒有實例傳遞出去作參數,因為 CarBuilder 是接口或抽象類,無法產生對象實例,實際傳遞的是 Builder 的子類,根據子類類型,決定生產內容)
- public class Director {
- public void constructSportsCar(CarBuilder builder){
- builder.setCarType(CarType.SPORTS_CAR);
- builder.setSeats(2);
- builder.setEngine(new Engine(2.0,0));
- builder.setGPS(new GPS());
- }
- public void constructCityCar(CarBuilder builder){
- builder.setCarType(CarType.CITY_CAR);
- builder.setSeats(4);
- builder.setEngine(new Engine(1.5,0));
- builder.setGPS(new GPS());
- }
- public void constructSUVCar(CarBuilder builder){
- builder.setCarType(CarType.SUV);
- builder.setSeats(4);
- builder.setEngine(new Engine(2.5,0));
- builder.setGPS(new GPS());
- }
- }
5、客戶端使用(最終結果從建造者對象中獲取,主管并不知道最終產品的類型)
- public class Client {
- public static void main(String[] args) {
- Director director = new Director();
- SportsCarBuilder builder = new SportsCarBuilder();
- director.constructSportsCar(builder);
- Car car = builder.getResult();
- System.out.println(car.toString());
- }
- }
適用場景
適用場景其實才是理解設計模式最重要的,只要知道這個業務場景需要什么模式,網上浪~程序員能不會嗎
- 使用建造者模式可避免重疊構造函數的出現。
假設你的構造函數中有 N 個可選參數,那 new 各種實例的時候就很麻煩,需要重載構造函數多次
- 當你希望使用代碼創建不同形式的產品 (例如石頭或木頭房屋) 時, 可使用建造者模式。
如果你需要創建的各種形式的產品, 它們的制造過程相似且僅有細節上的差異, 此時可使用建造者模式。
- 使用生成器構造組合樹或其他復雜對象。
建造者模式讓你能分步驟構造產品。你可以延遲執行某些步驟而不會影響最終產品。你甚至可以遞歸調用這些步驟, 這在創建對象樹時非常方便。
VS 抽象工廠
抽象工廠模式實現對產品家族的創建,一個產品家族是這樣的一系列產品:具有不同分類維度的產品組合,采用抽象工廠模式不需要關心抽象過程,只關心什么產品由什么工廠生產即可。而建造者模式則是要求按照指定的藍圖建造產品,它的主要目的是通過組裝零配件而生產一個新的產品。
最后
設計模式,這玩意看簡單的例子,肯定能看得懂,主要是結合自己的業務思考怎么應用,讓系統設計更完善,懂了每種模式后,可以找找各種框架源碼或在 github 搜搜相關內容,看看實際中是怎么應用的。
參考
refactoringguru.cn