1. 程式人生 > 實用技巧 >Luogu3845 [TJOI2007]球賽(Dilworth定理)題解

Luogu3845 [TJOI2007]球賽(Dilworth定理)題解

建造者模式

案例

生活中有一個常見的例子,沒有人買車會只買一個輪胎或者方向盤,大家買的都是一輛包含輪胎和方向盤等多個部件的完整汽車。如何將這些部件組裝成一輛完整的汽車並返回給使用者呢,下面我們用程式來模擬一下這一過程。

1.首先是汽車、輪胎和方向盤三個實體類:

汽車類

/**
 * 汽車
 */
public class Car {
    // 方向盤
    private SteeringWheel steeringWheel;
    // 輪胎
    private Doughnut doughnut;

    @Override
    public String toString() {
        return "汽車{" +
                "方向盤=" + steeringWheel +
                ", 輪胎=" + doughnut +
                '}';
    }
    // 省略getter/setter
}

輪胎類:

/**
 * 輪胎
 */
public class Doughnut {
    // 大小
    private String size;
    // 型別
    private String type;

    public Doughnut(String size, String type) {
        this.size = size;
        this.type = type;
    }

    @Override
    public String toString() {
        return " 輪胎{" +
                "大小='" + size + '\'' +
                ", 型別='" + type + '\'' +
                '}';
    }
}

方向盤類:

/**
 * 方向盤
 */
public class SteeringWheel {
    // 大小
    private String size;
    // 型別
    private String type;

    public SteeringWheel(String size, String type) {
        this.size = size;
        this.type = type;
    }

    @Override
    public String toString() {
        return " 方向盤{" +
                "大小='" + size + '\'' +
                ", 型別='" + type + '\'' +
                '}';
    }
}

2.然後我們直接生成汽車:

public class Main {
    public static void main(String[] args) {
        // 生成一個轎車
        // 製造汽車方向盤
        SteeringWheel steeringWheel1 = new SteeringWheel("中號", "三幅");
        // 製造輪胎
        Doughnut doughnut1 = new Doughnut("中號", "轎車輪胎");
        // 製造汽車
        Car car1 = new Car();
        car1.setSteeringWheel(steeringWheel1);
        car1.setDoughnut(doughnut1);
        System.out.println(car1);
        
        // 生成一個貨車
        SteeringWheel steeringWheel2 = new SteeringWheel("大號", "三幅");
        // 製造輪胎
        Doughnut doughnut2 = new Doughnut("大號", "貨車輪胎");
        // 製造汽車
        Car car2 = new Car();
        car2.setSteeringWheel(steeringWheel2);
        car2.setDoughnut(doughnut2);
        System.out.println(car2);
    }
}

可以看到生成汽車的過程直接由我們客戶端來完成了,這樣就使得客戶端和各個類都有耦合。同時我們,可以看出生產汽車的過程其實是一個比較流程化的事。而建造輪胎、方向盤等元件有可能是不盡相同的。並且,實際上建造輪胎和方向盤是較為麻煩的。基於此,接下來介紹建造者模式用於解決這一類問題。

模式介紹

建造者模式是設計模式的一種(建立型),將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

角色構成:

  • Builder(抽象建造者):它為建立一個產品Product物件的各個部件指定抽象介面,在該介面中一般宣告兩類方法,一類方法是buildPartX(),它們用於建立複雜物件的各個部件;另一類方法是getResult(),它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面。
  • ConcreteBuilder(具體建造者):它實現了Builder介面,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜物件,也可以提供一個方法返回建立好的複雜產品物件。
  • Product(產品角色):它是被構建的複雜物件,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。
  • Director(指揮者):指揮者又稱為導演類,它負責安排複雜物件的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()建造方法中呼叫建造者物件的部件構造與裝配方法,完成複雜物件的建造。客戶端一般只需要與指揮者進行互動,在客戶端確定具體建造者的型別,並例項化具體建造者物件(也可以通過配置檔案和反射機制),然後通過指揮者類的建構函式或者Setter方法將該物件傳入指揮者類中。

UML類圖:

特點:

建造者模式是較為複雜的建立型模式,它將客戶端與包含多個組成部分(或部件)的複雜物件的建立過程分離,客戶端無須知道複雜物件的內部組成部分與裝配方式,只需要知道所需建造者的型別即可。它關注如何一步一步建立一個的複雜物件,不同的具體建造者定義了不同的建立過程,且具體建造者相互獨立,增加新的建造者非常方便,無須修改已有程式碼,系統具有較好的擴充套件性。

程式碼改造

1.首先是三個實體類:因為和上面的實體類相同,就不貼程式碼了

2.抽象構造者和兩個具體實現類:

/**
 * 抽象構造者
 */
public abstract class CarBuilder {
    //建立產品物件
    protected Car car = new Car();

    public abstract void buildSteeringWheel();

    public abstract void buildDoughnut();

    //返回產品物件
    public Car getCar() {
        return car;
    }
}

小轎車類:

/**
 * 具體構造者:構造小轎車
 */
public class SedanCarBuilder extends CarBuilder {
    public void buildSteeringWheel() {
        car.setSteeringWheel(new SteeringWheel("中號", "三幅"));
    }

    public void buildDoughnut() {
        car.setDoughnut(new Doughnut("中號", "轎車輪胎"));
    }
}

貨車類:

/**
 * 具體構造者:構造貨車
 */
public class FreightCarBuilder extends CarBuilder {
    public void buildSteeringWheel() {
        car.setSteeringWheel(new SteeringWheel("大號", "三幅"));
    }

    public void buildDoughnut() {
        car.setDoughnut(new Doughnut("大號", "貨車輪胎"));
    }
}

3.汽車生產指揮者:

/**
 * 指揮者負責汽車的安裝過程
 */
public class Director {
    private CarBuilder builder;

    public Director(CarBuilder builder) {
        this.builder = builder;
    }

    public Car create() {
        builder.buildSteeringWheel();
        builder.buildDoughnut();
        return builder.getCar();
    }
}

3.客戶端使用:

/**
 * 客戶端通過指揮者來獲取汽車
 */
public class Main {
    public static void main(String[] args) {
        // 製造汽車
        Car car1 = new Director(new SedanCarBuilder()).create();
        System.out.println(car1);
        // 製造貨車
        Car car2 = new Director(new FreightCarBuilder()).create();
        System.out.println(car2);
    }
}

經過改造後,客戶端只需例項化指揮者類,指揮者類針對抽象建造者程式設計,客戶端根據需要傳入具體的建造者型別,指揮者將指導具體建造者一步一步構造一個完整的產品(逐步呼叫具體建造者的buildX()方法),相同的構造過程可以建立完全不同的產品。

模式應用

當我們看到Builder這個單詞的時候,很容易就能聯想到在我們 JDK 中的 StringBuilder 也是 Builder 結尾,其實它的append()方法就蘊含著建造者模式的思想,下面我們分析一下它的組成結構。

平時我們使用的最多的就是append()方法了:

public class Main {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder();
        builder.append("hello");
        System.out.println(builder);
    }
}

下面是它的 UML 類圖結構:

通過上圖我們可以分析出:

  • Appendable 介面定義了多個 append() 方法(抽象方法),即 Appendable 為抽象建造者,定義了抽象方法
  • AbstractStringBuilder實現了 Appendable 介面方法,這裡的 AbstractStringBuilder
    已經是就建造者,只是不能例項化
  • StringBuilder 即充當了指揮者角色,同時也充當了具體的建造者,建造方法的實現是由AbstractStringBuilder完成。

總結

1.主要優點:

  • 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。
  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,使用者使用不同的具體建造者即可得到不同的產品物件。由於指揮者類針對抽象建造者程式設計,增加新的具體建造者無須修改原有類庫的程式碼,系統擴充套件方便,符合“開閉原則”
  • 可以更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。

2.主要缺點:

  • 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。
  • 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和執行成本。

3.適用場景:

  • 需要生成的產品物件有複雜的內部結構,這些產品物件通常包含多個成員屬性。
  • 需要生成的產品物件的屬性相互依賴,需要指定其生成順序。
  • 物件的建立過程獨立於建立該物件的類。在建造者模式中通過引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類和客戶類中。
  • 隔離複雜物件的建立和使用,並使得相同的建立過程可以建立不同的產品。

參考資料

本篇文章github程式碼地址:https://github.com/Phoegel/design-pattern/tree/main/builder
轉載請說明出處,本篇部落格地址:https://www.cnblogs.com/phoegel/p/13922150.html