5.原型模式
5.1.課程目標
1、掌握原型模式和建造者模式的應(yīng)用場景
2、掌握原型模式的淺克隆和深克隆的寫法。
3、掌握建造者模式的基本寫法。
4、了解克隆是如何破壞單例的。
5、了解原型模式的優(yōu)、缺點
6、掌握建造者模式和工廠模式的區(qū)別。
5.2.內(nèi)容定位
1、已了解并掌握工廠模式的人群。
2、已了解并掌握單例模式。
3、聽說過原型模式,但不知道應(yīng)用場景的人群。
5.3.定義
原型模式(PrototypePattern)是指原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象,屬于創(chuàng)建型模式。
官方原文:Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
原型模式的核心在于拷貝原型對象。以系統(tǒng)中已存在的一個對象為原型,直接基于內(nèi)存二進制流進行拷貝,無需再經(jīng)歷耗時的對象初始化過程(不調(diào)用構(gòu)造函數(shù)),性能提升許多。當對象的構(gòu)建過程比較耗時時,可以利用當前系統(tǒng)中已存在的對象作為原型,對其進行克?。ㄒ话闶腔诙M制流的復(fù)制),躲避初始化過程,使得新對象的創(chuàng)建時間大大減少。下面,我們來看看原型模式類結(jié)構(gòu)圖:
<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301170301439.png" alt="image-20200301170301439" style="zoom: 50%;" />
從 UML 圖中,我們可以看到,原型模式 主要包含三個角色:
客戶(Client):客戶類提出創(chuàng)建對象的請求。
抽象原型(Prototype):規(guī)定拷貝接口。
具體原型(Concrete Prototype):被拷貝的對象。
注:對不通過 new 關(guān)鍵字,而是通過對象拷貝來實現(xiàn)創(chuàng)建對象的模式就稱作原型模式。
5.4.原型模式的應(yīng)用場景
你一定遇到過大篇幅getter、setter賦值的場景。
代碼非常工整,命名非常規(guī)范,注釋也寫的很全面,其實這就是原型模式的需求場景。但是,大家覺
得這樣的代碼優(yōu)雅嗎?我認為,這樣的代碼屬于純體力勞動。那原型模式,能幫助我們解決這樣的問題。
原型模式主要適用于以下場景:
1、類初始化消耗資源較多。
2、new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準備、訪問權(quán)限等)
3、構(gòu)造函數(shù)比較復(fù)雜。
4、循環(huán)體中生產(chǎn)大量對象時。
在 Spring 中,原型模式應(yīng)用得非常廣泛。例如 scope=“prototype”,在我們經(jīng)常用的
JSON.parseObject()也是一種原型模式。
5.5.原型模式的通用寫法(淺拷貝)
一個標準的原型模式代碼,應(yīng)該是這樣設(shè)計的。先創(chuàng)建原型IPrototype接口:
public interface IPrototype<T> {
T clone();
}
創(chuàng)建具體需要克隆的對象ConcretePrototype
public class ConcretePrototype implements IPrototype {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ConcretePrototype clone() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
return concretePrototype;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
測試代碼:
public class Client {
public static void main(String[] args) {
//創(chuàng)建原型對象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
System.out.println(prototype);
//拷貝原型對象
ConcretePrototype cloneType = prototype.clone();
System.out.println(cloneType);
}
}
運行結(jié)果:
ConcretePrototype{age=18, name='Tom'}
ConcretePrototype{age=18, name='Tom'}
這時候,有小伙伴就問了,原型模式就這么簡單嗎?對,就是這么簡單。在這個簡單的場景之下,看上
去操作好像變復(fù)雜了。但如果有幾百個屬性需要復(fù)制,那我們就可以一勞永逸。但是,上面的復(fù)制過程
是我們自己完成的,在實際編碼中,我們一般不會浪費這樣的體力勞動,JDK已經(jīng)幫我們實現(xiàn)了一個現(xiàn)
成的API,我們只需要實現(xiàn)Cloneable接口即可。來改造一下代碼,修改ConcretePrototype類:
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
重新運行,也會得到同樣的結(jié)果。
有了JDK的支持再多的屬性復(fù)制我們也能輕而易舉地搞定了。下面我
們再來做一個測試,給ConcretePrototype增加一個個人愛好的屬性hobbies:
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
修改客戶端測試代碼:
public class Client {
public static void main(String[] args) {
//創(chuàng)建原型對象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
hobbies.add("書法");
hobbies.add("美術(shù)");
prototype.setHobbies(hobbies);
//拷貝原型對象
ConcretePrototype cloneType = prototype.clone();
cloneType.getHobbies().add("技術(shù)控");
System.out.println("原型對象:" + prototype);
System.out.println("克隆對象:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("原型對象的愛好:" + prototype.getHobbies());
System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
}
運行結(jié)果:
原型對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
克隆對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
false
原型對象的愛好:[書法, 美術(shù), 技術(shù)控]
克隆對象的愛好:[書法, 美術(shù), 技術(shù)控]
true
我們給,復(fù)制后的克隆對象新增一項愛好,發(fā)現(xiàn)原型對象也發(fā)生了變化,這顯然不符合我們的預(yù)期。
因為我們希望克隆出來的對象應(yīng)該和原型對象是兩個獨立的對象,不應(yīng)該再有聯(lián)系了。從測試結(jié)果分析
來看,應(yīng)該是hobbies共用了一個內(nèi)存地址,意味著復(fù)制的不是值,而是引用的地址。這樣的話,如果我們修改任意一個對象中的屬性值,prototype 和cloneType的hobbies值都會改變。這就是我們常
說的淺克隆。只是完整復(fù)制了值類型數(shù)據(jù),沒有賦值引用對象。換言之,所有的引用對象仍然指向原來
的對象,顯然不是我們想要的結(jié)果。那如何解決這個問題呢?下面我們來看深度克隆繼續(xù)改造。
擴展知識:String對象在內(nèi)存中是不可變的(final類型),雖然克隆后,兩個對象String的引用指向的是同一個內(nèi)存地址,但是如果給克隆后的對象的String屬性改變值,那么相當于是在內(nèi)存中重新開辟了一塊內(nèi)存來存儲這個改變的值,而此時的String屬性對象就指向了該內(nèi)存值,所以這個時候克隆前和克隆后對象的String屬性是不一樣的)。
String 每次賦值,相當于new String()。
5.6.使用序列化實現(xiàn)深度克隆
在上面的基礎(chǔ)上我們繼續(xù)改造,來看代碼,增加一個deepClone()方法:
@Data
public class ConcretePrototype implements Cloneable,Serializable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public ConcretePrototype deepClone(){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype)ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
來看客戶端調(diào)用代碼:
public class Client {
public static void main(String[] args) {
//創(chuàng)建原型對象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
hobbies.add("書法");
hobbies.add("美術(shù)");
prototype.setHobbies(hobbies);
//拷貝原型對象
ConcretePrototype cloneType = prototype.deepClone();
cloneType.getHobbies().add("技術(shù)控");
System.out.println("原型對象:" + prototype);
System.out.println("克隆對象:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("原型對象的愛好:" + prototype.getHobbies());
System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
}
運行程序,我們發(fā)現(xiàn)得到了我們期望的結(jié)果:
原型對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù)])
克隆對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
false
原型對象的愛好:[書法, 美術(shù)]
克隆對象的愛好:[書法, 美術(shù), 技術(shù)控]
false
5.7.克隆破壞單例模式
如果我們克隆的目標的對象是單例對象,那意味著,深克隆就會破壞單例。實際上防止克隆破壞單
例解決思路非常簡單,禁止深克隆便可。要么你我們的單例類不實現(xiàn) Cloneable 接口;要么我們重寫
clone()方法,在clone方法中返回單例對象即可,具體代碼如下:
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
5.8.原型模式在源碼中的應(yīng)用
先來JDK中Cloneable接口:
public interface Cloneable {
}
接口定義還是很簡單的,我們找源碼其實只需要找到看哪些接口實現(xiàn)了 Cloneable 即可。來看
ArrayList類的實現(xiàn)。
Object方法
protected native Object clone() throws CloneNotSupportedException;
ArrayList是實現(xiàn)的clone方法
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
我們發(fā)現(xiàn)方法中只是將List中的元素循環(huán)遍歷了一遍。這個時候我們再思考一下,是不是這種形式
就是深克隆呢?其實用代碼驗證一下就知道了,繼續(xù)修改 ConcretePrototype 類,增加一個
deepCloneHobbies()方法:
@Data
public class ConcretePrototype implements Cloneable,Serializable {
...
public ConcretePrototype deepCloneHobbies(){
try {
ConcretePrototype result = (ConcretePrototype)super.clone();
result.hobbies = (List)((ArrayList)result.hobbies).clone();
return result;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
...
}
修改客戶端代碼:
public class Client {
public static void main(String[] args) {
...
//拷貝原型對象
ConcretePrototype cloneType = prototype.deepCloneHobbies();
...
}
}
運行也能得到期望的結(jié)果。但是這樣的代碼,其實是硬編碼,如果在對象中聲明了各種集合類型,
那每種情況都需要單獨處理。因此,深克隆的寫法,一般會直接用序列化來操作。
5.9.原型模式的優(yōu)缺點
優(yōu)點:
1、性能優(yōu)良,Java自帶的 原型模式 是基于內(nèi)存二進制流的拷貝,比直接new一個對象性能上提
升了許多。
2、可以使用深克隆方式保存對象的狀態(tài),使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來,簡化
了創(chuàng)建對象的過程,以便在需要的時候使用(例如恢復(fù)到歷史某一狀態(tài)),可輔助實現(xiàn)撤銷操作。
缺點:
1、需要為每一個類配置一個克隆方法。
2、克隆方法位于類的內(nèi)部,當對已有類進行改造的時候,需要修改代碼,違反了開閉原則。
3、在實現(xiàn)深克隆時需要編寫較為復(fù)雜的代碼,而且當對象之間存在多重嵌套引用時,為了實現(xiàn)深克
隆,每一層對象對應(yīng)的類都必須支持深克隆,實現(xiàn)起來會比較麻煩。因此,深拷貝、淺拷貝需要運用得
當。
6.0總結(jié)
克隆方式:1.序列化 反序列化 2.jsonobject 3淺克隆加賦值
淺克?。豪^承Cloneable接口的都是淺克隆。
深克隆兩種方式:序列化,轉(zhuǎn)JSON。
6.建造者模式
6.1.定義
建造者模式(Builder Pattern)是將一個復(fù)雜對象的構(gòu)建過程與它的表示分離,使得同樣的構(gòu)建過
程可以創(chuàng)建不同的表示,屬于創(chuàng)建型模式。使用建造者模式對于用戶而言只需指定需要建造的類型就可
以獲得對象,建造過程及細節(jié)不需要了解。
官方原文:Separate the construction of a complex object from its representation so that the same construction process can create different representations.
建造者模式適用于創(chuàng)建對象需要很多步驟,但是步驟的順序不一定固定。如果一個對象有非常復(fù)雜
的內(nèi)部結(jié)構(gòu)(很多屬性),可以將復(fù)雜對象的創(chuàng)建和使用進行分離。先來看一下建造者模式的類圖:
<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301183423834.png" alt="image-20200301183423834" style="zoom: 50%;" />
建造者模式的設(shè)計中主要有四個角色:
1、產(chǎn)品(Product):要創(chuàng)建的產(chǎn)品類對象
2、建造者抽象(Builder):建造者的抽象類,規(guī)范產(chǎn)品對象的各個組成部分的建造,一般由子類
實現(xiàn)具體的建造過程。
3、建造者(ConcreteBuilder):具體的Builder類,根據(jù)不同的業(yè)務(wù)邏輯,具體化對象的各個組成
部分的創(chuàng)建。
4、調(diào)用者(Director):調(diào)用具體的建造者,來創(chuàng)建對象的各個部分,在指導(dǎo)者中不涉及具體產(chǎn)品
的信息,只負責保證對象各部分完整創(chuàng)建或按某種順序創(chuàng)建。
6.2.建造者模式的應(yīng)用場景
建造者模式適用于一個具有較多的零件的復(fù)雜產(chǎn)品的創(chuàng)建過程,由于需求的變化,組成這個復(fù)雜產(chǎn)
品的各個零件經(jīng)常猛烈變化,但是它們的組合方式卻相對穩(wěn)定。
- 相同的方法,不同的執(zhí)行順序,產(chǎn)生不同的結(jié)果時
- 多個部件或零件,都可以裝配到一個對象中,但是產(chǎn)生的結(jié)果又不相同。
- 產(chǎn)品類非常復(fù)雜,或者產(chǎn)品類中的調(diào)用順序不同產(chǎn)生不同的作用。
- 當初始化一個對象特別復(fù)雜,參數(shù)多,而且很多參數(shù)都具有默認值時。
建造者模式,只關(guān)注用戶需要什么,將最少的關(guān)鍵字傳過來,生成你想要的結(jié)果。
實際順序是在build方法里面。那是順序和條件都確定了。每個順序和條件都分別存儲下來了。判斷有沒有,有就添加到product后面。當然就是先判斷條件再判斷order順序了
6.3.建造者模式的基本寫法
我們還是以課程為例,一個完整的課程需要由PPT課件、回放視頻、課堂筆記、課后作業(yè)組成,但
是這些內(nèi)容的設(shè)置順序可以隨意調(diào)整,我們用建造者模式來代入理解一下。首先我們創(chuàng)建一個需要構(gòu)造
的產(chǎn)品類Course:
@Data
public class Course {
private String name;
private String ppt;
private String video;
private String note;
private String homework;
}
然后創(chuàng)建建造者類CourseBuilder,將復(fù)雜的構(gòu)造過程封裝起來,構(gòu)造步驟由用戶決定:
public class CourseBuilder{
private Course course = new Course();
public void addName(String name) {
course.setName(name);
}
public void addPPT(String ppt) {
course.setPpt(ppt);
}
public void addVideo(String video) {
course.setVideo(video);
}
public void addNote(String note) {
course.setNote(note);
}
public void addHomework(String homework) {
course.setHomework(homework);
}
public Course build() {
return course;
}
}
編寫測試類:
public class Test {
public static void main(String[] args) {
CourseBuilder builder = new CourseBuilder();
builder.addName("設(shè)計模式");
builder.addPPT("【PPT課件】");
builder.addVideo("【回放視頻】");
builder.addNote("【課堂筆記】");
builder.addHomework("【課后作業(yè)】");
System.out.println(builder.build());
}
}
運行結(jié)果:
Course(name=設(shè)計模式, ppt=【PPT課件】, video=【回放視頻】, note=【課堂筆記】, homework=【課后作業(yè)】)
來看一下類結(jié)構(gòu)圖:
<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301184320779.png" alt="image-20200301184320779" style="zoom: 50%;" />
6.4.建造者模式的鏈式寫法
在平時的應(yīng)用中,建造者模式通常是采用鏈式編程的方式構(gòu)造對象,下面我們來一下演示代碼,修
改CourseBuilder類,將Course變?yōu)镃ourseBuilder的內(nèi)部類。然后,將構(gòu)造步驟添加進去,每完成一個步驟,都返回this:
public class CourseBuilder {
private Course course = new Course();
public CourseBuilder addName(String name) {
course.setName(name);
return this;
}
public CourseBuilder addPPT(String ppt) {
course.setPpt(ppt);
return this;
}
public CourseBuilder addVideo(String video) {
course.setVideo(video);
return this;
}
public CourseBuilder addNote(String note) {
course.setNote(note);
return this;
}
public CourseBuilder addHomework(String homework) {
course.setHomework(homework);
return this;
}
public Course build() {
return this.course;
}
@Data
public class Course {
private String name;
private String ppt;
private String video;
private String note;
private String homework;
}
}
客戶端使用:
public class Test {
public static void main(String[] args) {
CourseBuilder builder = new CourseBuilder()
.addName("設(shè)計模式")
.addPPT("【PPT課件】")
.addVideo("【回放視頻】")
.addNote("【課堂筆記】")
.addHomework("【課后作業(yè)】");
System.out.println(builder.build());
}
}
這樣寫法是不是很眼熟,好像在哪見過呢?后面我們分析建造者模式在源碼中的應(yīng)用大家就會明白。
接下來,我們再來看一下類圖的變化:
<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301184733802.png" alt="image-20200301184733802" style="zoom: 50%;" />
6.5.建造者模式應(yīng)用案例
下面我們再來看一個實戰(zhàn)案例,這個案例參考了開源框架JPA的SQL構(gòu)造模式。是否記得我們在構(gòu)
造SQL查詢條件的時候,需要根據(jù)不同的條件來拼接SQL字符串。如果查詢條件復(fù)雜的時候,我們SQL
拼接的過程也會變得非常復(fù)雜,從而給我們的代碼維護帶來非常大的困難。因此,我們用建造者類
QueryRuleSqlBuilder 將復(fù)雜的構(gòu)造 SQL 過程進行封裝,用 QueryRule 對象專門保存 SQL 查詢時的
條件,最后根據(jù)查詢條件,自動生成SQL語句。來看代碼,先創(chuàng)建QueryRule類:
/**
* QueryRule,主要功能用于構(gòu)造查詢條件
*/
public final class QueryRule implements Serializable
{
...
/**
* 添加升序規(guī)則
* @param propertyName
* @return
*/
public QueryRule addAscOrder(String propertyName) {
this.ruleList.add(new Rule(ASC_ORDER, propertyName));
return this;
}
public QueryRule andEqual(String propertyName, Object value) {
this.ruleList.add(new Rule(EQ, propertyName, new Object[] { value }).setAndOr(AND));
return this;
}
public QueryRule andLike(String propertyName, Object value) {
this.ruleList.add(new Rule(LIKE, propertyName, new Object[] { value }).setAndOr(AND));
return this;
}
...
}
然后,創(chuàng)建QueryRuleSqlBuilder類:
/**
* 根據(jù)QueryRule自動構(gòu)建sql語句
*/
public class QueryRuleSqlBuilder {
...
/**
* 處理like
* @param rule
*/
private void processLike(QueryRule.Rule rule) {
if (ArrayUtils.isEmpty(rule.getValues())) {
return;
}
Object obj = rule.getValues()[0];
if (obj != null) {
String value = obj.toString();
if (!StringUtils.isEmpty(value)) {
value = value.replace('*', '%');
obj = value;
}
}
add(rule.getAndOr(),rule.getPropertyName(),"like","%"+rule.getValues()[0]+"%");
}
/**
* 處理 =
* @param rule
*/
private void processEqual(QueryRule.Rule rule) {
if (ArrayUtils.isEmpty(rule.getValues())) {
return;
}
add(rule.getAndOr(),rule.getPropertyName(),"=",rule.getValues()[0]);
}
/**
* 處理 order by
* @param rule 查詢規(guī)則
*/
private void processOrder(Rule rule) {
switch (rule.getType()) {
case QueryRule.ASC_ORDER:
// propertyName非空
if (!StringUtils.isEmpty(rule.getPropertyName())) {
orders.add(Order.asc(rule.getPropertyName()));
}
break;
case QueryRule.DESC_ORDER:
// propertyName非空
if (!StringUtils.isEmpty(rule.getPropertyName())) {
orders.add(Order.desc(rule.getPropertyName()));
}
break;
default:
break;
}
}
...
}
創(chuàng)建Order類:
/**
* sql排序組件
*/
public class Order {
private boolean ascending; //升序還是降序
private String propertyName; //哪個字段升序,哪個字段降序
public String toString() {
return propertyName + ' ' + (ascending ? "asc" : "desc");
}
/**
* Constructor for Order.
*/
protected Order(String propertyName, boolean ascending) {
this.propertyName = propertyName;
this.ascending = ascending;
}
/**
* Ascending order
*
* @param propertyName
* @return Order
*/
public static Order asc(String propertyName) {
return new Order(propertyName, true);
}
/**
* Descending order
*
* @param propertyName
* @return Order
*/
public static Order desc(String propertyName) {
return new Order(propertyName, false);
}
}
編寫測試代碼:
public class Test {
public static void main(String[] args) {
QueryRule queryRule = QueryRule.getInstance();
queryRule.addAscOrder("age");
queryRule.andEqual("addr","Changsha");
queryRule.andLike("name","Tom");
QueryRuleSqlBuilder builder = new QueryRuleSqlBuilder(queryRule);
System.out.println(builder.builder("t_member"));
System.out.println("Params: " + Arrays.toString(builder.getValues()));
}
}
這樣一來,我們的客戶端代碼就非常清朗,來看運行結(jié)果:
select * from t_member where addr = ? and name like ? order by age asc
Params: [Changsha, %Tom%]
6.6.建造者模式在源碼中的體現(xiàn)
下面來看建造者模式在哪些源碼中有應(yīng)用呢?首先來看JDK的StringBuilder,它提供append()方
法,給我們開放構(gòu)造步驟,最后調(diào)用toString()方法就可以獲得一個構(gòu)造好的完整字符串,源碼如下:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
}
在MyBatis中也有體現(xiàn),比如CacheBuilder類。
同樣在 MyBatis 中,比如 SqlSessionFactoryBuilder 通過調(diào)用 build()方法獲得的是一個
SqlSessionFactory 類。
當然,在 Spring中自然也少不了,比如 BeanDefinitionBuilder 通過調(diào)用getBeanDefinition()方法獲得一個BeanDefinition對象。
6.7.建造者模式的優(yōu)缺點
建造者模式的優(yōu)點:
1、封裝性好,創(chuàng)建和使用分離;
2、擴展性好,建造類之間獨立、一定程度上解耦。
建造者模式的缺點:
1、產(chǎn)生多余的Builder對象;
2、產(chǎn)品內(nèi)部發(fā)生變化,建造者都要修改,成本較大。
6.8.建造者模式和工廠模式的區(qū)別
建造者模式和工廠模式的區(qū)別
1、建造者模式更加注重方法的調(diào)用順序,工廠模式注重于創(chuàng)建對象。
2、創(chuàng)建對象的力度不同,建造者模式創(chuàng)建復(fù)雜的對象,由各種復(fù)雜的部件組成,工廠模式創(chuàng)建出來
的都一樣。
3、關(guān)注重點不一樣,工廠模式模式只需要把對象創(chuàng)建出來就可以了,而建造者模式中不僅要創(chuàng)建出
這個對象,還要知道這個對象由哪些部件組成。
4、建造者模式根據(jù)建造過程中的順序不一樣,最終的對象部件組成也不一樣。
可以理解為工廠創(chuàng)建過程是靜態(tài)的,構(gòu)建者模式創(chuàng)建過程經(jīng)過外放而變成動態(tài)的。
6.9.總結(jié)
7.0.作業(yè)
1.用JSON方式實現(xiàn)一個原型模式的深克隆,并畫出UML圖。
一行代碼,比IO流簡單。
public ConcretePrototype deepCloneByJSON(){
try {
return JSON.parseObject(JSON.toJSONString(this), ConcretePrototype.class);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301182057508.png" alt="image-20200301182057508" style="zoom: 33%;" />
2.請列舉1-3個需要用到建造者模式的業(yè)務(wù)場景。
建造者模式:適用于對象創(chuàng)建需要動態(tài)拼接復(fù)雜屬性值的業(yè)務(wù)場景。
例如:SQL拼接,鏈式編程,NIO。