第五天:原型模式逞度、建造者模式

1. 原型模式

1.1 概述

原型模式是一種創(chuàng)建型設(shè)計(jì)模式抬旺,通過復(fù)制已有的實(shí)例來創(chuàng)建新對象,而不是通過實(shí)例化類來創(chuàng)建。這種模式適用于創(chuàng)建復(fù)雜對象或者頻繁創(chuàng)建和銷毀對象的場景。原型模式通過定義一個(gè)接口來聲明復(fù)制方法,具體的實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法健爬。

1.2 結(jié)構(gòu)

原型模式包含如下角色:

  • 原型接口(Prototype):定義一個(gè)接口,聲明克隆方法么介。
  • 具體原型類(ConcretePrototype):實(shí)現(xiàn)原型接口娜遵,提供具體的克隆功能。
  • 客戶端(Client):使用原型來創(chuàng)建對象壤短,而無需關(guān)心對象的具體類设拟。

接口類圖如下:

image.png

1.3 實(shí)現(xiàn)

原型模式的克隆分為淺克隆和深克隆。

淺克戮酶:創(chuàng)建一個(gè)新對象纳胧,新對象的屬性和原來對象完全相同,對于非基本類型屬性帘撰,仍指向原有屬性所指向的對象的內(nèi)存地址跑慕。

深克隆:創(chuàng)建一個(gè)新對象摧找,屬性中引用的其他對象也會(huì)被克隆核行,不再指向原有對象地址。

Java 中的 Object 類中提供了 clone() 方法來實(shí)現(xiàn)淺克隆蹬耘。 Cloneable 接口是上面的類圖中的原型接口芝雪,而實(shí)現(xiàn)了 Cloneable 接口的子實(shí)現(xiàn)類就是具體原型類。代碼如下:

// 具體原型類
public class ConcretePrototype implements Cloneable {
    private String field;

    public ConcretePrototype(String field) {
        System.out.println("具體原型對象創(chuàng)建完成综苔!");
        this.field = field;
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    @Override
    public ConcretePrototype clone() throws CloneNotSupportedException {
        System.out.println("具體原型復(fù)制成功惩系!");
        return (ConcretePrototype) super.clone();
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 輸出:具體的原型對象創(chuàng)建完成!
        ConcretePrototype original = new ConcretePrototype("Hello");
        // 輸出:具體原型復(fù)制成功休里!
        ConcretePrototype clone = original.clone();
        System.out.println("===修改前===");
        // 輸出:原始數(shù)據(jù):Hello
        System.out.println("原始數(shù)據(jù):" + original.getField());
        // 輸出:克隆數(shù)據(jù):Hello
        System.out.println("克隆數(shù)據(jù):" + clone.getField());
        System.out.println(original == clone);
        // 修改克隆對象的字段
        clone.setField("World");
        System.out.println("===修改后===");
        // 輸出:原始數(shù)據(jù):Hello
        System.out.println("原始數(shù)據(jù):" + original.getField());
        // 輸出:克隆數(shù)據(jù):World
        System.out.println("克隆數(shù)據(jù):" + clone.getField());
        // 判斷獲取到的兩個(gè)實(shí)例是否是同一個(gè)對象, 輸出:false
        System.out.println(original == clone);
    }
}

1.3.1 案例

用原型模式生成“三好學(xué)生”獎(jiǎng)狀

同一學(xué)校的“三好學(xué)生”獎(jiǎng)狀除了獲獎(jiǎng)學(xué)生不同赃承,其他都相同妙黍,可以使用原型模式復(fù)制多個(gè)“三好學(xué)生”獎(jiǎng)狀出來,然后在修改獎(jiǎng)狀上的學(xué)生即可瞧剖。

1.3.1.1 淺克隆

淺克隆是指在克隆對象時(shí)拭嫁,它會(huì)創(chuàng)建一個(gè)新的對象可免,新對象的非基本類型(引用類型)屬性將與原對象的相應(yīng)屬性指向相同的內(nèi)存地址。也就是說做粤,淺克隆只是簡單地復(fù)制對象的基本類型屬性的值浇借,而對于對象中的引用類型屬性,只是復(fù)制了引用怕品,而不是復(fù)制引用所指向的對象本身妇垢。

// 學(xué)生類
public class Student {
    // 學(xué)生的姓名
    private String name;
    // 學(xué)生的地址
    private String address;

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
// 獎(jiǎng)狀類
public class Citation implements Cloneable {
    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void show() {
        System.out.println(student.getName() + "同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評為三好學(xué)生肉康。特發(fā)此狀闯估!");
        System.out.println("獎(jiǎng)狀將會(huì)發(fā)往:" + student.getAddress() + "市");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation1 = new Citation();
        Student student1 = new Student("張三", "西安");
        citation1.setStudent(student1);
        // 復(fù)制獎(jiǎng)狀
        Citation citation2 = citation1.clone();
        // 獲取citation2獎(jiǎng)狀所屬學(xué)生對象
        Student student2 = citation2.getStudent();
        student2.setName("李四");
        student2.setAddress("上海");
        // 判斷student1對象和student2對象是否是同一個(gè)對象
        System.out.println("student1和student2是同一個(gè)對象?" + (student1 == student2));
        // 輸出:
        // 李四同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀吼和,被評為三好學(xué)生涨薪。特發(fā)此狀!
        // 獎(jiǎng)狀將會(huì)發(fā)往:上海市
        citation1.show();
        // 輸出:
        // 李四同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀炫乓,被評為三好學(xué)生刚夺。特發(fā)此狀!
        // 獎(jiǎng)狀將會(huì)發(fā)往:上海市
        citation2.show();
    }
}

student1student2 是同一個(gè)對象末捣,就會(huì)產(chǎn)生將 student1 對象中 name 屬性值改為“李四”侠姑,address 屬性值改為“上海”塔粒,兩個(gè) citation 對象中顯示的都是李四结借。這就是淺克隆的效果,對 具體原型類(Citation) 中的引用類型的屬性進(jìn)行引用的復(fù)制卒茬。這種情況需要使用深克隆船老,而進(jìn)行深克隆需要使用對象流。

1.3.1.2 深克隆

深克隆(Deep Clone)是指在克隆對象時(shí)圃酵,不僅要復(fù)制對象本身的值柳畔,還要復(fù)制對象內(nèi)部引用的所有對象,確惫停克隆出的對象與原對象完全獨(dú)立薪韩。與淺克隆相比,深克隆能避免引用類型數(shù)據(jù)的共享捌锭。

1.3.1.2.1 深克隆的兩種常見方法
  1. 實(shí)現(xiàn) Cloneable 接口并進(jìn)行手動(dòng)深拷貝
  2. 通過序列化機(jī)制實(shí)現(xiàn)深拷貝
1.3.1.2.2 手動(dòng)深拷貝

在這種方法中俘陷,所有可變的引用類型屬性都需要調(diào)用其 clone() 方法來進(jìn)行克隆。這種方法的優(yōu)勢是靈活性观谦,但可能需要手動(dòng)處理對象的各個(gè)層級拉盾,尤其是多級引用時(shí)實(shí)現(xiàn)起來會(huì)較為復(fù)雜。

// 學(xué)生類
public class Student implements Cloneable {
    // 學(xué)生的姓名
    private String name;
    // 學(xué)生的地址
    private String address;

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}
// 獎(jiǎng)狀類
public class Citation implements Cloneable {
    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void show() {
        System.out.println(student.getName() + "同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀豁状,被評為三好學(xué)生捉偏。特發(fā)此狀倒得!");
        System.out.println("獎(jiǎng)狀將會(huì)發(fā)往:" + student.getAddress() + "市");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        Citation clonedCitation = (Citation) super.clone();
        // 手動(dòng)克隆引用類型
        clonedCitation.setStudent(clonedCitation.getStudent().clone());
        return clonedCitation;
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation1 = new Citation();
        Student student1 = new Student("張三", "西安");
        citation1.setStudent(student1);
        // 復(fù)制獎(jiǎng)狀
        Citation citation2 = citation1.clone();
        // 獲取citation2獎(jiǎng)狀所屬學(xué)生對象
        Student student2 = citation2.getStudent();
        student2.setName("李四");
        student2.setAddress("上海");
        // 判斷student1對象和student2對象是否是同一個(gè)對象
        System.out.println("student1和student2是同一個(gè)對象?" + (student1 == student2));
        // 輸出:
        // 張三同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀夭禽,被評為三好學(xué)生霞掺。特發(fā)此狀!
        // 獎(jiǎng)狀將會(huì)發(fā)往:西安市
        citation1.show();
        // 輸出:
        // 李四同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀讹躯,被評為三好學(xué)生菩彬。特發(fā)此狀!
        // 獎(jiǎng)狀將會(huì)發(fā)往:上海市
        citation2.show();
    }
}
1.3.1.2.3 手動(dòng)深拷貝優(yōu)缺點(diǎn)
  1. 優(yōu)點(diǎn):
  • 靈活:可以精確控制每個(gè)引用屬性的拷貝方式蜀撑。
  1. 缺點(diǎn):
  • 實(shí)現(xiàn)復(fù)雜:每個(gè)引用類型屬性都需要手動(dòng)克隆挤巡,特別是嵌套對象時(shí),代碼復(fù)雜度會(huì)增加酷麦。
1.3.1.2.4 序列化方式深拷貝

這種方法借助 Java 的序列化機(jī)制矿卑,將對象序列化為字節(jié)流,再將其反序列化為一個(gè)新的對象沃饶,從而實(shí)現(xiàn)深拷貝母廷。該方法簡單且通用,適合于深拷貝復(fù)雜對象糊肤。

// 學(xué)生類
public class Student implements Serializable {
    // 學(xué)生的姓名
    private String name;
    // 學(xué)生的地址
    private String address;

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
// 獎(jiǎng)狀類
public class Citation implements Serializable {
    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public void show() {
        System.out.println(student.getName() + "同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀琴昆,被評為三好學(xué)生。特發(fā)此狀馆揉!");
        System.out.println("獎(jiǎng)狀將會(huì)發(fā)往:" + student.getAddress() + "市");
    }

    public Citation deepClone() throws IOException, ClassNotFoundException {
        // 寫入字節(jié)流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        // 讀取字節(jié)流
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Citation) ois.readObject();
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Citation citation1 = new Citation();
        Student student1 = new Student("張三", "西安");
        citation1.setStudent(student1);
        // 復(fù)制獎(jiǎng)狀
        Citation citation2 = citation1.deepClone();
        // 獲取citation2獎(jiǎng)狀所屬學(xué)生對象
        Student student2 = citation2.getStudent();
        student2.setName("李四");
        student2.setAddress("上海");
        // 判斷student1對象和student2對象是否是同一個(gè)對象
        System.out.println("student1和student2是同一個(gè)對象业舍?" + (student1 == student2));
        // 輸出:
        // 張三同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評為三好學(xué)生升酣。特發(fā)此狀舷暮!
        // 獎(jiǎng)狀將會(huì)發(fā)往:西安市
        citation1.show();
        // 輸出:
        // 李四同學(xué):在2024學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評為三好學(xué)生噩茄。特發(fā)此狀下面!
        // 獎(jiǎng)狀將會(huì)發(fā)往:上海市
        citation2.show();
    }
}

注意:Citation 類和 Student 類必須實(shí)現(xiàn) Serializable 接口,否則會(huì)拋 NotSerializableException 異常绩聘。

1.3.1.2.5 序列化方式深拷貝優(yōu)缺點(diǎn)
  1. 優(yōu)點(diǎn):
  • 簡單:不需要手動(dòng)編寫克隆邏輯沥割,序列化和反序列化會(huì)自動(dòng)處理整個(gè)對象圖的拷貝。
  • 通用性:適用于對象圖中包含多層級引用關(guān)系的復(fù)雜對象凿菩。
  1. 缺點(diǎn):
  • 需要類實(shí)現(xiàn) Serializable 接口机杜。
  • 性能較低:序列化和反序列化相對較慢,可能對性能有影響衅谷,特別是大對象時(shí)椒拗。

1.4 原型模式優(yōu)缺點(diǎn)

  1. 優(yōu)點(diǎn)
  • 性能優(yōu)化:通過克隆已經(jīng)存在的對象,可以避免重復(fù)初始化和復(fù)雜構(gòu)造過程会喝,從而提高性能陡叠。
  • 動(dòng)態(tài)性:原型模式可以在運(yùn)行時(shí)決定需要克隆的具體對象,支持靈活的對象創(chuàng)建肢执。
  • 簡化對象創(chuàng)建:對于復(fù)雜對象枉阵,使用原型克隆相較于構(gòu)造器更加簡單直觀,尤其是當(dāng)對象的構(gòu)造需要很多步驟時(shí)预茄。
  1. 缺點(diǎn)
  • 實(shí)現(xiàn)復(fù)雜性:需要實(shí)現(xiàn) clone 方法兴溜,對于一些復(fù)雜對象,可能涉及深拷貝和淺拷貝的實(shí)現(xiàn)耻陕,使得代碼復(fù)雜拙徽。
  • 依賴問題:如果原型對象內(nèi)部含有復(fù)雜的依賴關(guān)系或者不可變對象,克隆時(shí)可能會(huì)導(dǎo)致狀態(tài)不一致的問題诗宣。
  • 限制:在 Java 中膘怕,如果原型類沒有實(shí)現(xiàn) Cloneable 接口,調(diào)用 clone() 方法會(huì)拋出異常召庞。需要額外的處理岛心。

1.5 總結(jié)

原型模式是一種有效的對象創(chuàng)建方式,尤其適用于對象構(gòu)造復(fù)雜或者頻繁創(chuàng)建和銷毀的情況篮灼。通過克隆已有的實(shí)例忘古,可以簡化對象創(chuàng)建過程,提高性能诅诱,但在實(shí)現(xiàn)時(shí)需要關(guān)注克隆的復(fù)雜性和對象狀態(tài)的一致性問題髓堪。

2. 建造者模式

2.1 概述

建造者模式(Builder Pattern)是一種創(chuàng)建型設(shè)計(jì)模式,用于將復(fù)雜對象的創(chuàng)建過程與其表示分離娘荡。它允許逐步構(gòu)建對象干旁,并根據(jù)需要?jiǎng)討B(tài)選擇其組成部分,最終通過一個(gè)建造者將對象“組裝”起來它改。建造者模式的核心在于將一個(gè)復(fù)雜對象的構(gòu)建與它的表示分離開疤孕,這樣同樣的構(gòu)建過程可以創(chuàng)建不同的表示。

2.2 結(jié)構(gòu)

  • 產(chǎn)品(Product):表示要構(gòu)建的復(fù)雜對象央拖。
  • 抽象建造者(Builder):定義構(gòu)建對象的步驟祭阀,通常包括設(shè)置部件的抽象方法。
  • 具體建造者(ConcreteBuilder):實(shí)現(xiàn) Builder 接口鲜戒,完成實(shí)際對象的創(chuàng)建步驟专控,并返回最終的復(fù)雜對象。
  • 指揮者(Director):控制構(gòu)建過程的邏輯遏餐,將建造的步驟按特定順序執(zhí)行伦腐。
  • 客戶端(Client):使用 DirectorBuilder 來生成產(chǎn)品。

接口類圖如下:

image.png

2.3 實(shí)現(xiàn)

// 漢堡類失都,表示要構(gòu)建的產(chǎn)品
public class Burger {
    // 面包
    private String bread;
    // 肉餅
    private String patty;
    // 沙拉
    private String salad;
    // 醬料
    private String sauce;

    public void setBread(String bread) {
        this.bread = bread;
    }

    public void setPatty(String patty) {
        this.patty = patty;
    }

    public void setSalad(String salad) {
        this.salad = salad;
    }

    public void setSauce(String sauce) {
        this.sauce = sauce;
    }

    @Override
    public String toString() {
        return "漢堡里面有:" + bread + "柏蘑," + patty + "幸冻," + salad + "和" + sauce;
    }
}
// 抽象的建造者,定義了構(gòu)建漢堡的步驟
public interface BurgerBuilder {
    void buildBread();

    void buildPatty();

    void buildSalad();

    void buildSauce();
    
    // 返回構(gòu)建好的漢堡
    Burger getBurger();
}
// 具體建造者咳焚,構(gòu)建一款經(jīng)典漢堡
public class ClassicBurgerBuilder implements BurgerBuilder {
    private final Burger burger;

    public ClassicBurgerBuilder() {
        this.burger = new Burger();
    }

    @Override
    public void buildBread() {
        burger.setBread("白面包");
        System.out.println("使用白面包...");
    }

    @Override
    public void buildPatty() {
        burger.setPatty("牛肉餅");
        System.out.println("添加牛肉餅...");
    }

    @Override
    public void buildSalad() {
        burger.setSalad("生菜");
        System.out.println("添加生菜...");
    }

    @Override
    public void buildSauce() {
        burger.setSauce("番茄醬");
        System.out.println("添加番茄醬...");
    }

    @Override
    public Burger getBurger() {
        return this.burger;
    }
}
// 具體建造者洽损,構(gòu)建一款素食漢堡
public class VeggieBurgerBuilder implements BurgerBuilder {
    private final Burger burger;

    public VeggieBurgerBuilder() {
        this.burger = new Burger();  // 初始化漢堡
    }

    @Override
    public void buildBread() {
        burger.setBread("全麥面包");
        System.out.println("使用全麥面包...");
    }

    @Override
    public void buildPatty() {
        burger.setPatty("豆腐餅");
        System.out.println("添加豆腐餅...");
    }

    @Override
    public void buildSalad() {
        burger.setSalad("黃瓜");
        System.out.println("添加黃瓜...");
    }

    @Override
    public void buildSauce() {
        burger.setSauce("芥末醬");
        System.out.println("添加芥末醬...");
    }

    @Override
    public Burger getBurger() {
        return this.burger;
    }
}
// 指揮者,負(fù)責(zé)控制構(gòu)建的過程
public class BurgerDirector {
    private final BurgerBuilder burgerBuilder;

    public BurgerDirector(BurgerBuilder burgerBuilder) {
        this.burgerBuilder = burgerBuilder;
    }
    // 控制建造過程
    public Burger makeBurger() {
        burgerBuilder.buildBread();
        burgerBuilder.buildPatty();
        burgerBuilder.buildSalad();
        burgerBuilder.buildSauce();
        // 返回構(gòu)建好的漢堡
        return burgerBuilder.getBurger();
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) {
        // 創(chuàng)建具體的建造者
        BurgerBuilder builder1 = new ClassicBurgerBuilder();
        BurgerBuilder builder2 = new VeggieBurgerBuilder();
        // 創(chuàng)建指揮者并傳入建造者
        BurgerDirector director1 = new BurgerDirector(builder1);
        BurgerDirector director2 = new BurgerDirector(builder2);
        // ===開始構(gòu)建經(jīng)典漢堡===
        // 使用白面包...
        // 添加牛肉餅...
        // 添加生菜...
        // 添加番茄醬...
        System.out.println("===開始構(gòu)建經(jīng)典漢堡===");
        Burger burger1 = director1.makeBurger();
        // ===開始構(gòu)建素食漢堡===
        // 使用全麥面包...
        // 添加豆腐餅...
        // 添加黃瓜...
        // 添加芥末醬...
        System.out.println("===開始構(gòu)建素食漢堡===");
        Burger burger2 = director2.makeBurger();
        System.out.println("======");
        // 輸出:構(gòu)建好的經(jīng)典漢堡: 漢堡里面有:白面包革半,牛肉餅碑定,生菜和番茄醬
        System.out.println("構(gòu)建好的經(jīng)典漢堡: " + burger1);
        // 輸出:構(gòu)建好的素食漢堡: 漢堡里面有:全麥面包,豆腐餅又官,黃瓜和芥末醬
        System.out.println("構(gòu)建好的素食漢堡: " + burger2);
    }
}

注意:
上面示例是 Builder 模式的常規(guī)用法延刘,指揮者類 Director 在建造者模式中具有很重要的作用,它用于指導(dǎo)具體構(gòu)建者如何構(gòu)建產(chǎn)品六敬,控制調(diào)用先后次序碘赖,并向調(diào)用者返回完整的產(chǎn)品類,但是有些情況下需要簡化系統(tǒng)結(jié)構(gòu)外构,可以把指揮者類和抽象建造者進(jìn)行結(jié)合崖疤。

// 抽象的建造者和指揮者,定義了構(gòu)建漢堡的步驟并且負(fù)責(zé)控制構(gòu)建的過程
public abstract class BurgerBuilder {
    protected Burger burger = new Burger();

    abstract void buildBread();

    abstract void buildPatty();

    abstract void buildSalad();

    abstract void buildSauce();

    abstract Burger getBurger();

    // 控制建造過程
    public Burger makeBurger() {
        this.buildBread();
        this.buildPatty();
        this.buildSalad();
        this.buildSauce();
        // 返回構(gòu)建好的漢堡
        return this.getBurger();
    }
}
// 具體建造者典勇,構(gòu)建一款經(jīng)典漢堡
public class ClassicBurgerBuilder extends BurgerBuilder {
    private final Burger burger;

    public ClassicBurgerBuilder() {
        this.burger = new Burger();
    }

    @Override
    public void buildBread() {
        burger.setBread("白面包");
        System.out.println("使用白面包...");
    }

    @Override
    public void buildPatty() {
        burger.setPatty("牛肉餅");
        System.out.println("添加牛肉餅...");
    }

    @Override
    public void buildSalad() {
        burger.setSalad("生菜");
        System.out.println("添加生菜...");
    }

    @Override
    public void buildSauce() {
        burger.setSauce("番茄醬");
        System.out.println("添加番茄醬...");
    }

    @Override
    public Burger getBurger() {
        return this.burger;
    }
}
// 具體建造者劫哼,構(gòu)建一款素食漢堡
public class VeggieBurgerBuilder extends BurgerBuilder {
    private final Burger burger;

    public VeggieBurgerBuilder() {
        this.burger = new Burger();  // 初始化漢堡
    }

    @Override
    public void buildBread() {
        burger.setBread("全麥面包");
        System.out.println("使用全麥面包...");
    }

    @Override
    public void buildPatty() {
        burger.setPatty("豆腐餅");
        System.out.println("添加豆腐餅...");
    }

    @Override
    public void buildSalad() {
        burger.setSalad("黃瓜");
        System.out.println("添加黃瓜...");
    }

    @Override
    public void buildSauce() {
        burger.setSauce("芥末醬");
        System.out.println("添加芥末醬...");
    }

    @Override
    public Burger getBurger() {
        return this.burger;
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) {
        // 創(chuàng)建具體的建造者
        BurgerBuilder builder1 = new ClassicBurgerBuilder();
        BurgerBuilder builder2 = new VeggieBurgerBuilder();
        // ===開始構(gòu)建經(jīng)典漢堡===
        // 使用白面包...
        // 添加牛肉餅...
        // 添加生菜...
        // 添加番茄醬...
        System.out.println("===開始構(gòu)建經(jīng)典漢堡===");
        Burger burger1 = builder1.makeBurger();
        // ===開始構(gòu)建素食漢堡===
        // 使用全麥面包...
        // 添加豆腐餅...
        // 添加黃瓜...
        // 添加芥末醬...
        System.out.println("===開始構(gòu)建素食漢堡===");
        Burger burger2 = builder2.makeBurger();
        System.out.println("======");
        // 輸出:構(gòu)建好的經(jīng)典漢堡: 漢堡里面有:白面包,牛肉餅割笙,生菜和番茄醬
        System.out.println("構(gòu)建好的經(jīng)典漢堡: " + burger1);
        // 輸出:構(gòu)建好的素食漢堡: 漢堡里面有:全麥面包权烧,豆腐餅,黃瓜和芥末醬
        System.out.println("構(gòu)建好的素食漢堡: " + burger2);
    }
}

這樣做確實(shí)簡化了系統(tǒng)結(jié)構(gòu)伤溉,但同時(shí)也加重了抽象建造者類的職責(zé)般码,也不是太符合單一職責(zé)原則,如果建造過程過于復(fù)雜乱顾,建議還是封裝到 Director 中板祝。

2.4 建造者模式優(yōu)缺點(diǎn)

  1. 優(yōu)點(diǎn)
  • 更好的控制對象創(chuàng)建過程:通過分步驟創(chuàng)建對象,每一步可以自由選擇不同的細(xì)節(jié)走净,最終得到精確定制的產(chǎn)品券时。
  • 代碼更簡潔、更靈活:客戶端無需關(guān)心復(fù)雜對象的創(chuàng)建過程伏伯,只需通過指揮者調(diào)用建造者來構(gòu)建對象橘洞。
  • 解耦創(chuàng)建和表示:在建造者模式中,客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié)说搅,將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過程解耦炸枣,使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品對象。
  • 擴(kuò)展性好:通過實(shí)現(xiàn)不同的 ConcreteBuilder,可以靈活創(chuàng)建多種表示的產(chǎn)品适肠,而無需修改現(xiàn)有的指揮者和產(chǎn)品類霍衫。因此也就不會(huì)對原有功能引入風(fēng)險(xiǎn)。符合開閉原則侯养。
  1. 缺點(diǎn)
  • 產(chǎn)品內(nèi)部構(gòu)建過程較復(fù)雜時(shí)可能增加復(fù)雜性:雖然建造者模式解耦了對象的構(gòu)建慕淡,但如果產(chǎn)品本身過于復(fù)雜,可能需要很多 Builder 子類來完成具體的構(gòu)建任務(wù)沸毁,反而增加系統(tǒng)的復(fù)雜度。
  • 需要有穩(wěn)定的構(gòu)建過程:如果產(chǎn)品的構(gòu)建過程頻繁變化傻寂,可能需要頻繁修改 Builder 接口及其實(shí)現(xiàn)類息尺。
  • 需要有共同點(diǎn):建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn),其組成部分相似疾掰,如果產(chǎn)品之間的差異性很大搂誉,則不適合使用建造者模式,因此其使用范圍受到一定的限制静檬。

2.5 使用場景

  • 需要分步驟創(chuàng)建復(fù)雜對象:對象由多個(gè)部分組成炭懊,并且對象的創(chuàng)建步驟依賴于某些條件或者順序。
  • 不同表示的對象使用同樣的構(gòu)建過程:同樣的創(chuàng)建步驟可以構(gòu)建出不同的對象表示拂檩。
  • 需要將對象的創(chuàng)建過程與表示分離:在需要解耦對象的構(gòu)建與它的具體表示時(shí)侮腹,使用建造者模式可以減少耦合。

2.6 模式擴(kuò)展

建造者模式除了上面的用途外稻励,在開發(fā)中還有一個(gè)常用的使用方式父阻,就是當(dāng)一個(gè)類構(gòu)造器需要傳入很多參數(shù)時(shí),如果創(chuàng)建這個(gè)類的實(shí)例望抽,代碼可讀性會(huì)非常差加矛,而且很容易引入錯(cuò)誤,此時(shí)就可以利用建造者模式進(jìn)行重構(gòu)煤篙。

重構(gòu)前代碼如下:

// 電腦類
public class Computer {
    // CPU
    private String CPU;
    // 屏幕
    private String screen;
    // 內(nèi)存條
    private String memory;
    // 主板
    private String mainboard;

    public Computer(String CPU, String screen, String memory, String mainboard) {
        this.CPU = CPU;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

    public String getCPU() {
        return CPU;
    }

    public void setCPU(String CPU) {
        this.CPU = CPU;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getMainboard() {
        return mainboard;
    }

    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) {
        // 構(gòu)建電腦對象
        Computer computer = new Computer("intel", "三星屏幕", "金士頓", "華碩");
        // 輸出:Computer{CPU='intel', screen='三星屏幕', memory='金士頓', mainboard='華碩'}
        System.out.println(computer);
    }
}

上面在客戶端代碼中構(gòu)建 Computer 對象斟览,傳遞了四個(gè)參數(shù),如果參數(shù)更多呢辑奈?代碼的可讀性及使用的成本就是比較高苛茂。

重構(gòu)后代碼:

// 電腦類
public class Computer {
    // CPU
    private final String CPU;
    // 屏幕
    private final String screen;
    // 內(nèi)存條
    private final String memory;
    // 主板
    private final String mainboard;

    private Computer(Builder builder) {
        this.CPU = builder.CPU;
        this.screen = builder.screen;
        this.memory = builder.memory;
        this.mainboard = builder.mainboard;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }

    public static final class Builder {
        private String CPU;
        private String screen;
        private String memory;
        private String mainboard;

        public Builder() {
        }

        public Builder CPU(String CPU) {
            this.CPU = CPU;
            return this;
        }

        public Builder screen(String screen) {
            this.screen = screen;
            return this;
        }

        public Builder memory(String memory) {
            this.memory = memory;
            return this;
        }

        public Builder mainboard(String mainboard) {
            this.mainboard = mainboard;
            return this;
        }
        public Computer build(){
            return new Computer(this);
        }
    }
}
// 客戶端代碼
public class Client {
    public static void main(String[] args) {
        // 構(gòu)建電腦對象
        Computer computer1 = new Computer.Builder()
                .CPU("intel")
                .mainboard("華碩")
                .memory("金士頓")
                .screen("三星")
                .build();
        Computer computer2 = new Computer.Builder()
                .CPU("AMD")
                .mainboard("微星")
                .screen("LG")
                .memory("芝奇")
                .build();
        // 輸出:Computer{CPU='intel', screen='三星屏幕', memory='金士頓', mainboard='華碩'}
        System.out.println(computer1);
        // 輸出:Computer{CPU='AMD', screen='LG', memory='芝奇', mainboard='微星'}
        System.out.println(computer2);
    }
}

重構(gòu)后的代碼在使用起來更方便,某種程度上也可以提高開發(fā)效率鸠窗。從軟件設(shè)計(jì)上味悄,對程序員的要求比較高。

2.7 總結(jié)

建造者模式通過將復(fù)雜對象的構(gòu)建過程與它的最終表示解耦塌鸯,為對象創(chuàng)建提供了靈活性和可擴(kuò)展性侍瑟。它可以靈活地構(gòu)建多種不同的對象,適用于對象需要分步驟創(chuàng)建并且具有多個(gè)不同表示的場景。但當(dāng)對象的構(gòu)建過程非常復(fù)雜或頻繁變化時(shí)涨颜,建造者模式的實(shí)現(xiàn)可能會(huì)變得冗長和復(fù)雜费韭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庭瑰,隨后出現(xiàn)的幾起案子星持,更是在濱河造成了極大的恐慌,老刑警劉巖弹灭,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件督暂,死亡現(xiàn)場離奇詭異,居然都是意外死亡穷吮,警方通過查閱死者的電腦和手機(jī)逻翁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捡鱼,“玉大人八回,你說我怎么就攤上這事〖菡” “怎么了缠诅?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乍迄。 經(jīng)常有香客問我管引,道長,這世上最難降的妖魔是什么闯两? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任汉匙,我火速辦了婚禮,結(jié)果婚禮上生蚁,老公的妹妹穿的比我還像新娘噩翠。我一直安慰自己,他們只是感情好邦投,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布伤锚。 她就那樣靜靜地躺著,像睡著了一般志衣。 火紅的嫁衣襯著肌膚如雪屯援。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天念脯,我揣著相機(jī)與錄音狞洋,去河邊找鬼。 笑死绿店,一個(gè)胖子當(dāng)著我的面吹牛吉懊,可吹牛的內(nèi)容都是我干的庐橙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼借嗽,長吁一口氣:“原來是場噩夢啊……” “哼态鳖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恶导,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤浆竭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后惨寿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邦泄,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年裂垦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顺囊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缸废,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驶社,到底是詐尸還是另有隱情企量,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布亡电,位于F島的核電站届巩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏份乒。R本人自食惡果不足惜恕汇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望或辖。 院中可真熱鬧瘾英,春花似錦、人聲如沸颂暇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耳鸯。三九已至湿蛔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間县爬,已是汗流浹背阳啥。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留财喳,地道東北人察迟。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卷拘。 傳聞我的和親對象是個(gè)殘疾皇子喊废,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容