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)心對象的具體類设拟。
接口類圖如下:
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();
}
}
student1
和 student2
是同一個(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 深克隆的兩種常見方法
- 實(shí)現(xiàn)
Cloneable
接口并進(jìn)行手動(dòng)深拷貝 - 通過序列化機(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)
- 優(yōu)點(diǎn):
- 靈活:可以精確控制每個(gè)引用屬性的拷貝方式蜀撑。
- 缺點(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)
- 優(yōu)點(diǎn):
- 簡單:不需要手動(dòng)編寫克隆邏輯沥割,序列化和反序列化會(huì)自動(dòng)處理整個(gè)對象圖的拷貝。
- 通用性:適用于對象圖中包含多層級引用關(guān)系的復(fù)雜對象凿菩。
- 缺點(diǎn):
- 需要類實(shí)現(xiàn)
Serializable
接口机杜。 - 性能較低:序列化和反序列化相對較慢,可能對性能有影響衅谷,特別是大對象時(shí)椒拗。
1.4 原型模式優(yōu)缺點(diǎn)
- 優(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í)预茄。
- 缺點(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):使用
Director
和Builder
來生成產(chǎn)品。
接口類圖如下:
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)
- 優(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)。符合開閉原則侯养。
- 缺點(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ù)雜费韭。