5种创建者模式
筆記來(lái)源于黑馬程序員但不僅僅是黑馬
創(chuàng)建者模式
- 創(chuàng)建者模式
- 4.1 單例設(shè)計(jì)模式
- 4.1.1 單例模式的結(jié)構(gòu)
- 4.1.2 單例模式的實(shí)現(xiàn)
- 4.1.3 存在的問(wèn)題
- 4.1.3.1 問(wèn)題演示
- 4.1.3.2 問(wèn)題的解決
- 4.1.4 JDK源碼解析-Runtime類
- 4.2 工廠模式
- 4.2.1 概述
- 4.2.2 簡(jiǎn)單工廠模式
- 4.2.2.1 結(jié)構(gòu)
- 4.2.2.2 實(shí)現(xiàn)
- 4.2.2.4 優(yōu)缺點(diǎn)
- 4.2.2.3 擴(kuò)展
- 4.2.3 工廠方法模式
- 4.2.3.1 概念
- 4.2.3.2 結(jié)構(gòu)
- 4.2.3.3 實(shí)現(xiàn)
- 4.2.3.4 優(yōu)缺點(diǎn)
- 4.2.4 抽象工廠模式
- 4.2.4.1 概念
- 4.2.4.2 結(jié)構(gòu)
- 4.2.4.2 實(shí)現(xiàn)
- 4.2.4.3 優(yōu)缺點(diǎn)
- 4.2.4.4 使用場(chǎng)景
- **最佳實(shí)踐**
- 4.2.5 模式擴(kuò)展
- 4.2.6 JDK源碼解析-Collection.iterator方法
- 4.3 原型模式
- 4.3.1 概述
- 4.3.2 結(jié)構(gòu)
- 4.3.3 實(shí)現(xiàn)
- 4.3.4 案例
- 4.3.5 使用場(chǎng)景
- 4.3.6 擴(kuò)展(深克隆)
- 4,創(chuàng)建型模式
- 4.2 工廠模式
- 4.2.1 概述
- 4.2.2 簡(jiǎn)單工廠模式
- 4.2.2.1 結(jié)構(gòu)
- 4.2.2.2 實(shí)現(xiàn)
- 4.2.2.4 優(yōu)缺點(diǎn)
- 4.2.2.3 擴(kuò)展
- 4.2.3 工廠方法模式
- 4.2.3.1 概念
- 4.2.3.2 結(jié)構(gòu)
- 4.2.3.3 實(shí)現(xiàn)
- 4.2.3.4 優(yōu)缺點(diǎn)
- 4.2.4 抽象工廠模式
- 4.2.4.1 概念
- 4.2.4.2 結(jié)構(gòu)
- 4.2.4.2 實(shí)現(xiàn)
- 4.2.4.3 優(yōu)缺點(diǎn)
- 4.2.4.4 使用場(chǎng)景
- **最佳實(shí)踐**
- 4.2.5 模式擴(kuò)展
- 4.2.6 JDK源碼解析-Collection.iterator方法
- 4.3 原型模式
- 4.3.1 概述
- 4.3.2 結(jié)構(gòu)
- 4.3.3 實(shí)現(xiàn)
- 4.3.4 案例
- 4.3.5 使用場(chǎng)景
- 4.3.6 擴(kuò)展(深克隆)
- 4.5 建造者模式
- 4.4.1 概述
- 4.4.2 結(jié)構(gòu)
- 4.4.3 實(shí)例
- 4.4.4 優(yōu)缺點(diǎn)
- 4.4.5 使用場(chǎng)景
- 4.4.6 模式擴(kuò)展
- 4.6 創(chuàng)建者模式對(duì)比
- 4.6.1 工廠方法模式VS建造者模式
- 4.6.2 抽象工廠模 式VS建造者模式
創(chuàng)建者模式
創(chuàng)建型模式的主要關(guān)注點(diǎn)是“怎樣創(chuàng)建對(duì)象?”,它的主要特點(diǎn)是“將對(duì)象的創(chuàng)建與使用分離”。
這樣可以降低系統(tǒng)的耦合度,使用者不需要關(guān)注對(duì)象的創(chuàng)建細(xì)節(jié)。
創(chuàng)建型模式分為:
- 單例模式
- 工廠方法模式
- 抽象工廠模式
- 原型模式
- 建造者模式
4.1 單例設(shè)計(jì)模式
單例模式(Singleton Pattern)是 Java 中最簡(jiǎn)單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。
這種模式涉及到一個(gè)單一的類,該類負(fù)責(zé)創(chuàng)建自己的對(duì)象,同時(shí)確保只有單個(gè)對(duì)象被創(chuàng)建。這個(gè)類提供了一種訪問(wèn)其唯一的對(duì)象的方式,可以直接訪問(wèn),不需要實(shí)例化該類的對(duì)象。
4.1.1 單例模式的結(jié)構(gòu)
單例模式的主要有以下角色:
- 單例類。只能創(chuàng)建一個(gè)實(shí)例的類
- 訪問(wèn)類。使用單例類
4.1.2 單例模式的實(shí)現(xiàn)
單例設(shè)計(jì)模式分類兩種:
? 餓漢式:類加載就會(huì)導(dǎo)致該單實(shí)例對(duì)象被創(chuàng)建
? 懶漢式:類加載不會(huì)導(dǎo)致該單實(shí)例對(duì)象被創(chuàng)建,而是首次使用該對(duì)象時(shí)才會(huì)創(chuàng)建
餓漢式-方式1(靜態(tài)變量方式)
/*** 餓漢式* 靜態(tài)變量創(chuàng)建類的對(duì)象*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}//在成員位置創(chuàng)建該類的對(duì)象private static Singleton instance = new Singleton();//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {return instance;} }說(shuō)明:
? 該方式在成員位置聲明Singleton類型的靜態(tài)變量,并創(chuàng)建Singleton類的對(duì)象instance。instance對(duì)象是隨著類的加載而創(chuàng)建的。如果該對(duì)象足夠大的話,而一直沒(méi)有使用就會(huì)造成內(nèi)存的浪費(fèi)。
餓漢式-方式2(靜態(tài)代碼塊方式)
/*** 惡漢式* 在靜態(tài)代碼塊中創(chuàng)建該類對(duì)象*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}//在成員位置創(chuàng)建該類的對(duì)象private static Singleton instance;static {instance = new Singleton();}//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {return instance;} }說(shuō)明:
? 該方式在成員位置聲明Singleton類型的靜態(tài)變量,而對(duì)象的創(chuàng)建是在靜態(tài)代碼塊中,也是對(duì)著類的加載而創(chuàng)建。所以和餓漢式的方式1基本上一樣,當(dāng)然該方式也存在內(nèi)存浪費(fèi)問(wèn)題。
懶漢式-方式1(線程不安全)
/*** 懶漢式* 線程不安全*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}//在成員位置創(chuàng)建該類的對(duì)象private static Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;} }說(shuō)明:
? 從上面代碼我們可以看出該方式在成員位置聲明Singleton類型的靜態(tài)變量,并沒(méi)有進(jìn)行對(duì)象的賦值操作,那么什么時(shí)候賦值的呢?當(dāng)調(diào)用getInstance()方法獲取Singleton類的對(duì)象的時(shí)候才創(chuàng)建Singleton類的對(duì)象,這樣就實(shí)現(xiàn)了懶加載的效果。但是,如果是多線程環(huán)境,會(huì)出現(xiàn)線程安全問(wèn)題。
懶漢式-方式2(線程安全)
/*** 懶漢式* 線程安全*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}//在成員位置創(chuàng)建該類的對(duì)象private static Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static synchronized Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;} }說(shuō)明:
? 該方式也實(shí)現(xiàn)了懶加載效果,同時(shí)又解決了線程安全問(wèn)題。但是在getInstance()方法上添加了synchronized關(guān)鍵字,導(dǎo)致該方法的執(zhí)行效果特別低。從上面代碼我們可以看出,其實(shí)就是在初始化instance的時(shí)候才會(huì)出現(xiàn)線程安全問(wèn)題,一旦初始化完成就不存在了。
懶漢式-方式3(雙重檢查鎖)
再來(lái)討論一下懶漢模式中加鎖的問(wèn)題,對(duì)于 getInstance() 方法來(lái)說(shuō),絕大部分的操作都是讀操作,讀操作是線程安全的,所以我們沒(méi)必讓每個(gè)線程必須持有鎖才能調(diào)用該方法,我們需要調(diào)整加鎖的時(shí)機(jī)。由此也產(chǎn)生了一種新的實(shí)現(xiàn)模式:雙重檢查鎖模式
/*** 雙重檢查方式*/ public class Singleton { //私有構(gòu)造方法private Singleton() {}private static Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)例if(instance == null) {synchronized (Singleton.class) {//搶到鎖之后再次判斷是否為nullif(instance == null) {instance = new Singleton();}}}return instance;} }雙重檢查鎖模式是一種非常好的單例實(shí)現(xiàn)模式,解決了單例、性能、線程安全問(wèn)題,上面的雙重檢測(cè)鎖模式看上去完美無(wú)缺,其實(shí)是存在問(wèn)題,在多線程的情況下,可能會(huì)出現(xiàn)空指針問(wèn)題,出現(xiàn)問(wèn)題的原因是JVM在實(shí)例化對(duì)象的時(shí)候會(huì)進(jìn)行優(yōu)化和指令重排序操作。
要解決雙重檢查鎖模式帶來(lái)空指針異常的問(wèn)題,只需要使用 volatile 關(guān)鍵字, volatile 關(guān)鍵字可以保證可見(jiàn)性和有序性。
/*** 雙重檢查方式*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}private static volatile Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {//第一次判斷,如果instance不為null,不進(jìn)入搶鎖階段,直接返回實(shí)際if(instance == null) {synchronized (Singleton.class) {//搶到鎖之后再次判斷是否為空if(instance == null) {instance = new Singleton();}}}return instance;} }小結(jié):
添加 volatile 關(guān)鍵字之后的雙重檢查鎖模式是一種比較好的單例實(shí)現(xiàn)模式,能夠保證在多線程的情況下線程安全也不會(huì)有性能問(wèn)題。
懶漢式-方式4(靜態(tài)內(nèi)部類方式)
靜態(tài)內(nèi)部類單例模式中實(shí)例由內(nèi)部類創(chuàng)建,由于 JVM 在加載外部類的過(guò)程中, 是不會(huì)加載靜態(tài)內(nèi)部類的, 只有內(nèi)部類的屬性/方法被調(diào)用時(shí)才會(huì)被加載, 并初始化其靜態(tài)屬性。靜態(tài)屬性由于被 static 修飾,保證只被實(shí)例化一次,并且嚴(yán)格保證實(shí)例化順序。
/*** 靜態(tài)內(nèi)部類方式*/ public class Singleton {//私有構(gòu)造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {return SingletonHolder.INSTANCE;} }說(shuō)明:
? 第一次加載Singleton類時(shí)不會(huì)去初始化INSTANCE,只有第一次調(diào)用getInstance,虛擬機(jī)加載SingletonHolder
并初始化INSTANCE,這樣不僅能確保線程安全,也能保證 Singleton 類的唯一性。
小結(jié):
? 靜態(tài)內(nèi)部類單例模式是一種優(yōu)秀的單例模式,是開(kāi)源項(xiàng)目中比較常用的一種單例模式。在沒(méi)有加任何鎖的情況下,保證了多線程下的安全,并且沒(méi)有任何性能影響和空間的浪費(fèi)。
枚舉方式
枚舉類實(shí)現(xiàn)單例模式是極力推薦的單例實(shí)現(xiàn)模式,因?yàn)槊杜e類型是線程安全的,并且只會(huì)裝載一次,設(shè)計(jì)者充分的利用了枚舉的這個(gè)特性來(lái)實(shí)現(xiàn)單例模式,枚舉的寫法非常簡(jiǎn)單,而且枚舉類型是所用單例實(shí)現(xiàn)中唯一一種不會(huì)被破壞的單例實(shí)現(xiàn)模式。
說(shuō)明:
? 枚舉方式屬于惡漢式方式。
單例模式的優(yōu)點(diǎn)
由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開(kāi)支,特別是一個(gè)對(duì)象需要頻繁地創(chuàng)建、銷毀時(shí),而且創(chuàng)建或銷毀時(shí)性能又無(wú)法優(yōu)化,單例模式的優(yōu)勢(shì)就非常明顯。
由于單例模式只生成一個(gè)實(shí)例,所以減少了系統(tǒng)的性能開(kāi)銷,當(dāng)一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí),如讀取配置、產(chǎn)生其他依賴對(duì)象時(shí),則可以通過(guò)在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象,然后用永久駐留內(nèi)存的方式來(lái)解決(在Java EE中采用單例模式時(shí)需要注意JVM垃圾回收機(jī)制)。
單例模式可以避免對(duì)資源的多重占用,例如一個(gè)寫文件動(dòng)作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫操作。
單例模式可以在系統(tǒng)設(shè)置全局的訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn),例如可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理
缺點(diǎn):
不適用于變化的對(duì)象,如果同一類型的對(duì)象總是要在不同的用例場(chǎng)景發(fā)生變化,單例就會(huì)引起數(shù)據(jù)的錯(cuò)誤,不能保存彼此的狀態(tài)。
由于單利模式中沒(méi)有抽象層,因此單例類的擴(kuò)展有很大的困難。
單例類的職責(zé)過(guò)重,在一定程度上違背了“單一職責(zé)原則”。
濫用單例將帶來(lái)一些負(fù)面問(wèn)題,如為了節(jié)省資源將數(shù)據(jù)庫(kù)連接池對(duì)象設(shè)計(jì)為的單例類,可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過(guò)多而出現(xiàn)連接池溢出;如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為是垃圾而被回收,這將導(dǎo)致對(duì)象狀態(tài)的丟失
4.1.3 存在的問(wèn)題
4.1.3.1 問(wèn)題演示
破壞單例模式:
使上面定義的單例類(Singleton)可以創(chuàng)建多個(gè)對(duì)象,枚舉方式除外。有兩種方式,分別是序列化和反射。
-
序列化反序列化
Singleton類:
public class Singleton implements Serializable {//私有構(gòu)造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {return SingletonHolder.INSTANCE;} }Test類:
public class Test {public static void main(String[] args) throws Exception {//往文件中寫對(duì)象//writeObject2File();//從文件中讀取對(duì)象Singleton s1 = readObjectFromFile();Singleton s2 = readObjectFromFile();//判斷兩個(gè)反序列化后的對(duì)象是否是同一個(gè)對(duì)象System.out.println(s1 == s2);}private static Singleton readObjectFromFile() throws Exception {//創(chuàng)建對(duì)象輸入流對(duì)象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));//第一個(gè)讀取Singleton對(duì)象Singleton instance = (Singleton) ois.readObject();return instance;}public static void writeObject2File() throws Exception {//獲取Singleton類的對(duì)象Singleton instance = Singleton.getInstance();//創(chuàng)建對(duì)象輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));//將instance對(duì)象寫出到文件中oos.writeObject(instance);} }上面代碼運(yùn)行結(jié)果是false,表明序列化和反序列化已經(jīng)破壞了單例設(shè)計(jì)模式。
-
反射
Singleton類:
public class Singleton {//私有構(gòu)造方法private Singleton() {}private static volatile Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {if(instance != null) {return instance;}synchronized (Singleton.class) {if(instance != null) {return instance;}instance = new Singleton();return instance;}} }Test類:
public class Test {public static void main(String[] args) throws Exception {//獲取Singleton類的字節(jié)碼對(duì)象Class clazz = Singleton.class;//獲取Singleton類的私有無(wú)參構(gòu)造方法對(duì)象Constructor constructor = clazz.getDeclaredConstructor();//取消訪問(wèn)檢查constructor.setAccessible(true);//創(chuàng)建Singleton類的對(duì)象s1Singleton s1 = (Singleton) constructor.newInstance();//創(chuàng)建Singleton類的對(duì)象s2Singleton s2 = (Singleton) constructor.newInstance();//判斷通過(guò)反射創(chuàng)建的兩個(gè)Singleton對(duì)象是否是同一個(gè)對(duì)象System.out.println(s1 == s2);} }上面代碼運(yùn)行結(jié)果是false,表明序列化和反序列化已經(jīng)破壞了單例設(shè)計(jì)模式
注意:枚舉方式不會(huì)出現(xiàn)這兩個(gè)問(wèn)題。
4.1.3.2 問(wèn)題的解決
-
序列化、反序列方式破壞單例模式的解決方法
在Singleton類中添加readResolve()方法,在反序列化時(shí)被反射調(diào)用,如果定義了這個(gè)方法,就返回這個(gè)方法的值,如果沒(méi)有定義,則返回新new出來(lái)的對(duì)象。
Singleton類:
public class Singleton implements Serializable {//私有構(gòu)造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}/*** 下面是為了解決序列化反序列化破解單例模式*/private Object readResolve() {return SingletonHolder.INSTANCE;} }源碼解析:
ObjectInputStream類
public final Object readObject() throws IOException, ClassNotFoundException{...// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(false);//重點(diǎn)查看readObject0方法..... }private Object readObject0(boolean unshared) throws IOException {...try {switch (tc) {...case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));//重點(diǎn)查看readOrdinaryObject方法...}} finally {depth--;bin.setBlockDataMode(oldMode);} }private Object readOrdinaryObject(boolean unshared) throws IOException {...//isInstantiable 返回true,執(zhí)行 desc.newInstance(),通過(guò)反射創(chuàng)建新的單例類,obj = desc.isInstantiable() ? desc.newInstance() : null; ...// 在Singleton類中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法執(zhí)行結(jié)果為trueif (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {// 通過(guò)反射調(diào)用 Singleton 類中的 readResolve 方法,將返回值賦值給rep變量// 這樣多次調(diào)用ObjectInputStream類中的readObject方法,繼而就會(huì)調(diào)用我們定義的readResolve方法,所以返回的是同一個(gè)對(duì)象。Object rep = desc.invokeReadResolve(obj);...}return obj; } -
反射方式破解單例的解決方法
public class Singleton {//私有構(gòu)造方法private Singleton() {/*反射破解單例模式需要添加的代碼*/if(instance != null) {throw new RuntimeException();}}private static volatile Singleton instance;//對(duì)外提供靜態(tài)方法獲取該對(duì)象public static Singleton getInstance() {if(instance != null) {return instance;}synchronized (Singleton.class) {if(instance != null) {return instance;}instance = new Singleton();return instance;}} }說(shuō)明:
? 這種方式比較好理解。當(dāng)通過(guò)反射方式調(diào)用構(gòu)造方法進(jìn)行創(chuàng)建創(chuàng)建時(shí),直接拋異常。不運(yùn)行此中操作。
4.1.4 JDK源碼解析-Runtime類
Runtime類就是使用的單例設(shè)計(jì)模式。
通過(guò)源代碼查看使用的是哪兒種單例模式
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}... }從上面源代碼中可以看出Runtime類使用的是餓漢式(靜態(tài)屬性)方式來(lái)實(shí)現(xiàn)單例模式的。
使用Runtime類中的方法
public class RuntimeDemo {public static void main(String[] args) throws IOException {//獲取Runtime類對(duì)象Runtime runtime = Runtime.getRuntime();//返回 Java 虛擬機(jī)中的內(nèi)存總量。System.out.println(runtime.totalMemory());//返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量。System.out.println(runtime.maxMemory());//創(chuàng)建一個(gè)新的進(jìn)程執(zhí)行指定的字符串命令,返回進(jìn)程對(duì)象Process process = runtime.exec("ipconfig");//獲取命令執(zhí)行后的結(jié)果,通過(guò)輸入流獲取InputStream inputStream = process.getInputStream();byte[] arr = new byte[1024 * 1024* 100];int b = inputStream.read(arr);System.out.println(new String(arr,0,b,"gbk"));} }4.2 工廠模式
4.2.1 概述
需求:設(shè)計(jì)一個(gè)咖啡店點(diǎn)餐系統(tǒng)。
設(shè)計(jì)一個(gè)咖啡類(Coffee),并定義其兩個(gè)子類(美式咖啡【AmericanCoffee】和拿鐵咖啡【LatteCoffee】);再設(shè)計(jì)一個(gè)咖啡店類(CoffeeStore),咖啡店具有點(diǎn)咖啡的功能。
具體類的設(shè)計(jì)如下:
在java中,萬(wàn)物皆對(duì)象,這些對(duì)象都需要?jiǎng)?chuàng)建,如果創(chuàng)建的時(shí)候直接new該對(duì)象,就會(huì)對(duì)該對(duì)象耦合嚴(yán)重,假如我們要更換對(duì)象,所有new對(duì)象的地方都需要修改一遍,這顯然違背了軟件設(shè)計(jì)的開(kāi)閉原則。如果我們使用工廠來(lái)生產(chǎn)對(duì)象,我們就只和工廠打交道就可以了,徹底和對(duì)象解耦,如果要更換對(duì)象,直接在工廠里更換該對(duì)象即可,達(dá)到了與對(duì)象解耦的目的;所以說(shuō),工廠模式最大的優(yōu)點(diǎn)就是:解耦。
在本教程中會(huì)介紹三種工廠的使用
- 簡(jiǎn)單工廠模式(不屬于GOF的23種經(jīng)典設(shè)計(jì)模式)
- 工廠方法模式
- 抽象工廠模式
4.2.2 簡(jiǎn)單工廠模式
簡(jiǎn)單工廠不是一種設(shè)計(jì)模式,反而比較像是一種編程習(xí)慣。
4.2.2.1 結(jié)構(gòu)
簡(jiǎn)單工廠包含如下角色:
- 抽象產(chǎn)品 :定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品 :實(shí)現(xiàn)或者繼承抽象產(chǎn)品的子類
- 具體工廠 :提供了創(chuàng)建產(chǎn)品的方法,調(diào)用者通過(guò)該方法來(lái)獲取產(chǎn)品。
4.2.2.2 實(shí)現(xiàn)
現(xiàn)在使用簡(jiǎn)單工廠對(duì)上面案例進(jìn)行改進(jìn),類圖如下:
工廠類代碼如下:
public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffee;} }工廠(factory)處理創(chuàng)建對(duì)象的細(xì)節(jié),一旦有了SimpleCoffeeFactory,CoffeeStore類中的orderCoffee()就變成此對(duì)象的客戶,后期如果需要Coffee對(duì)象直接從工廠中獲取即可。這樣也就解除了和Coffee實(shí)現(xiàn)類的耦合,同時(shí)又產(chǎn)生了新的耦合,CoffeeStore對(duì)象和SimpleCoffeeFactory工廠對(duì)象的耦合,工廠對(duì)象和商品對(duì)象的耦合。
后期如果再加新品種的咖啡,我們勢(shì)必要需求修改SimpleCoffeeFactory的代碼,違反了開(kāi)閉原則。工廠類的客戶端可能有很多,比如創(chuàng)建美團(tuán)外賣等,這樣只需要修改工廠類的代碼,省去其他的修改操作。
4.2.2.4 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
封裝了創(chuàng)建對(duì)象的過(guò)程,可以通過(guò)參數(shù)直接獲取對(duì)象。把對(duì)象的創(chuàng)建和業(yè)務(wù)邏輯層分開(kāi),這樣以后就避免了修改客戶代碼,如果要實(shí)現(xiàn)新產(chǎn)品直接修改工廠類,而不需要在原代碼中修改,這樣就降低了客戶代碼修改的可能性,更加容易擴(kuò)展。
缺點(diǎn):
增加新產(chǎn)品時(shí)還是需要修改工廠類的代碼,違背了“開(kāi)閉原則”。
4.2.2.3 擴(kuò)展
靜態(tài)工廠
在開(kāi)發(fā)中也有一部分人將工廠類中的創(chuàng)建對(duì)象的功能定義為靜態(tài)的,這個(gè)就是靜態(tài)工廠模式,它也不是23種設(shè)計(jì)模式中的。代碼如下:
public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffe;} }4.2.3 工廠方法模式
針對(duì)上例中的缺點(diǎn),使用工廠方法模式就可以完美的解決,完全遵循開(kāi)閉原則。
4.2.3.1 概念
定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪個(gè)產(chǎn)品類對(duì)象。工廠方法使一個(gè)產(chǎn)品類的實(shí)例化延遲到其工廠的子類。
4.2.3.2 結(jié)構(gòu)
工廠方法模式的主要角色:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,調(diào)用者通過(guò)它訪問(wèn)具體工廠的工廠方法來(lái)創(chuàng)建產(chǎn)品。
- 具體工廠(ConcreteFactory):主要是實(shí)現(xiàn)抽象工廠中的抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品(ConcreteProduct):實(shí)現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來(lái)創(chuàng)建,它同具體工廠之間一一對(duì)應(yīng)。
4.2.3.3 實(shí)現(xiàn)
使用工廠方法模式對(duì)上例進(jìn)行改進(jìn),類圖如下:
代碼如下:
抽象工廠:
public interface CoffeeFactory {Coffee createCoffee(); }具體工廠:
public class LatteCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new LatteCoffee();} }public class AmericanCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new AmericanCoffee();} }咖啡店類:
public class CoffeeStore {private CoffeeFactory factory;public CoffeeStore(CoffeeFactory factory) {this.factory = factory;}public Coffee orderCoffee(String type) {Coffee coffee = factory.createCoffee();coffee.addMilk();coffee.addsugar();return coffee;} }從以上的編寫的代碼可以看到,要增加產(chǎn)品類時(shí)也要相應(yīng)地增加工廠類,不需要修改工廠類的代碼了,這樣就解決了簡(jiǎn)單工廠模式的缺點(diǎn)。
工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象。由于使用了多態(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。
4.2.3.4 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 用戶只需要知道具體工廠的名稱就可得到所要的產(chǎn)品,無(wú)須知道產(chǎn)品的具體創(chuàng)建過(guò)程;
- 在系統(tǒng)增加新的產(chǎn)品時(shí)只需要添加具體產(chǎn)品類和對(duì)應(yīng)的具體工廠類,無(wú)須對(duì)原工廠進(jìn)行任何修改,滿足開(kāi)閉原則;
缺點(diǎn):
- 每增加一個(gè)產(chǎn)品就要增加一個(gè)具體產(chǎn)品類和一個(gè)對(duì)應(yīng)的具體工廠類,這增加了系統(tǒng)的復(fù)雜度。
工廠方法模式的優(yōu)點(diǎn)
首先,良好的封裝性,代碼結(jié)構(gòu)清晰。一個(gè)對(duì)象創(chuàng)建是有條件約束的,如一個(gè)調(diào)用者需要一個(gè)具體的產(chǎn)品對(duì)象,只要知道這個(gè)產(chǎn)品的類名(或約束字符串)就可以了,不用知道創(chuàng)建對(duì)象的艱辛過(guò)程,降低模塊間的耦合。其次,工廠方法模式的擴(kuò)展性非常優(yōu)秀。在增加產(chǎn)品類的情況下,只要適當(dāng)?shù)匦薷木唧w的工廠類或擴(kuò)展一個(gè)工廠類,就可以完成“擁抱變化”。工廠類不用任何修改就可完成系統(tǒng)擴(kuò)展。再次,屏蔽產(chǎn)品類。這一特點(diǎn)非常重要,產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者都不需要關(guān)心,它只需要關(guān)心產(chǎn)品的接口,只要接口保持不變,系統(tǒng)中的上層模塊就不要發(fā)生變化。因?yàn)楫a(chǎn)品類的實(shí)例化工作是由工廠類負(fù)責(zé)的,一個(gè)產(chǎn)品對(duì)象具體由哪一個(gè)產(chǎn)品生成是由工廠類決定的。在數(shù)據(jù)庫(kù)開(kāi)發(fā)中,大家應(yīng)該能夠深刻體會(huì)到工廠方法模式的好處:如果使用JDBC連接數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)從MySQL切換到Oracle,需要改動(dòng)的地方就是切換一下驅(qū)動(dòng)名稱(前提條件是SQL語(yǔ)句是標(biāo)準(zhǔn)語(yǔ)句),其他的都不需要修改,這是工廠方法模式靈活性的一個(gè)直接案例。
最后,工廠方法模式是典型的解耦框架。高層模塊值需要知道產(chǎn)品的抽象類,其他的實(shí)現(xiàn)類都不用關(guān)心,符合迪米特法則,我不需要的就不要去交流;也符合依賴倒置原則,只依賴產(chǎn)品類的抽象;當(dāng)然也符合里氏替換原則,使用產(chǎn)品子類替換產(chǎn)品父類,沒(méi)問(wèn)題!
4.2.4 抽象工廠模式
前面介紹的工廠方法模式中考慮的是一類產(chǎn)品的生產(chǎn),如畜牧場(chǎng)只養(yǎng)動(dòng)物、電視機(jī)廠只生產(chǎn)電視機(jī)、傳智播客只培養(yǎng)計(jì)算機(jī)軟件專業(yè)的學(xué)生等。
這些工廠只生產(chǎn)同種類產(chǎn)品,同種類產(chǎn)品稱為同等級(jí)產(chǎn)品,也就是說(shuō):工廠方法模式只考慮生產(chǎn)同等級(jí)的產(chǎn)品,但是在現(xiàn)實(shí)生活中許多工廠是綜合型的工廠,能生產(chǎn)多等級(jí)(種類) 的產(chǎn)品,如電器廠既生產(chǎn)電視機(jī)又生產(chǎn)洗衣機(jī)或空調(diào),大學(xué)既有軟件專業(yè)又有生物專業(yè)等。
本節(jié)要介紹的抽象工廠模式將考慮多等級(jí)產(chǎn)品的生產(chǎn),將同一個(gè)具體工廠所生產(chǎn)的位于不同等級(jí)的一組產(chǎn)品稱為一個(gè)產(chǎn)品族,下圖所示橫軸是產(chǎn)品等級(jí),也就是同一類產(chǎn)品;縱軸是產(chǎn)品族,也就是同一品牌的產(chǎn)品,同一品牌的產(chǎn)品產(chǎn)自同一個(gè)工廠。
4.2.4.1 概念
是一種為訪問(wèn)類提供一個(gè)創(chuàng)建一組相關(guān)或相互依賴對(duì)象的接口,且訪問(wèn)類無(wú)須指定所要產(chǎn)品的具體類就能得到同族的不同等級(jí)的產(chǎn)品的模式結(jié)構(gòu)。
抽象工廠模式是工廠方法模式的升級(jí)版本,工廠方法模式只生產(chǎn)一個(gè)等級(jí)的產(chǎn)品,而抽象工廠模式可生產(chǎn)多個(gè)等級(jí)的產(chǎn)品。
4.2.4.2 結(jié)構(gòu)
抽象工廠模式的主要角色如下:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,它包含多個(gè)創(chuàng)建產(chǎn)品的方法,可以創(chuàng)建多個(gè)不同等級(jí)的產(chǎn)品。
- 具體工廠(Concrete Factory):主要是實(shí)現(xiàn)抽象工廠中的多個(gè)抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能,抽象工廠模式有多個(gè)抽象產(chǎn)品。
- 具體產(chǎn)品(ConcreteProduct):實(shí)現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來(lái)創(chuàng)建,它 同具體工廠之間是多對(duì)一的關(guān)系。
4.2.4.2 實(shí)現(xiàn)
現(xiàn)咖啡店業(yè)務(wù)發(fā)生改變,不僅要生產(chǎn)咖啡還要生產(chǎn)甜點(diǎn),如提拉米蘇、抹茶慕斯等,要是按照工廠方法模式,需要定義提拉米蘇類、抹茶慕斯類、提拉米蘇工廠、抹茶慕斯工廠、甜點(diǎn)工廠類,很容易發(fā)生類爆炸情況。其中拿鐵咖啡、美式咖啡是一個(gè)產(chǎn)品等級(jí),都是咖啡;提拉米蘇、抹茶慕斯也是一個(gè)產(chǎn)品等級(jí);拿鐵咖啡和提拉米蘇是同一產(chǎn)品族(也就是都屬于意大利風(fēng)味),美式咖啡和抹茶慕斯是同一產(chǎn)品族(也就是都屬于美式風(fēng)味)。所以這個(gè)案例可以使用抽象工廠模式實(shí)現(xiàn)。類圖如下:
代碼如下:
抽象工廠:
public interface DessertFactory {Coffee createCoffee();Dessert createDessert(); }具體工廠:
//美式甜點(diǎn)工廠 public class AmericanDessertFactory implements DessertFactory {public Coffee createCoffee() {return new AmericanCoffee();}public Dessert createDessert() {return new MatchaMousse();} } //意大利風(fēng)味甜點(diǎn)工廠 public class ItalyDessertFactory implements DessertFactory {public Coffee createCoffee() {return new LatteCoffee();}public Dessert createDessert() {return new Tiramisu();} }如果要加同一個(gè)產(chǎn)品族的話,只需要再加一個(gè)對(duì)應(yīng)的工廠類即可,不需要修改其他的類。
4.2.4.3 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。
缺點(diǎn):
當(dāng)產(chǎn)品族中需要增加一個(gè)新的產(chǎn)品時(shí),所有的工廠類都需要進(jìn)行修改。
4.2.4.4 使用場(chǎng)景
-
當(dāng)需要?jiǎng)?chuàng)建的對(duì)象是一系列相互關(guān)聯(lián)或相互依賴的產(chǎn)品族時(shí),如電器工廠中的電視機(jī)、洗衣機(jī)、空調(diào)等。
-
系統(tǒng)中有多個(gè)產(chǎn)品族,但每次只使用其中的某一族產(chǎn)品。如有人只喜歡穿某一個(gè)品牌的衣服和鞋。
-
系統(tǒng)中提供了產(chǎn)品的類庫(kù),且所有產(chǎn)品的接口相同,客戶端不依賴產(chǎn)品實(shí)例的創(chuàng)建細(xì)節(jié)和內(nèi)部結(jié)構(gòu)。
如:輸入法換皮膚,一整套一起換。生成不同操作系統(tǒng)的程序。
最佳實(shí)踐
一個(gè)模式在什么情況下才能夠使用,是很多讀者比較困惑的地方。抽象工廠模式是一個(gè)
簡(jiǎn)單的模式,使用的場(chǎng)景非常多,大家在軟件產(chǎn)品開(kāi)發(fā)過(guò)程中,涉及不同操作系統(tǒng)的時(shí)候,
都可以考慮使用抽象工廠模式,例如一個(gè)應(yīng)用,需要在三個(gè)不同平臺(tái)(Windows、Linux、
Android(Google發(fā)布的智能終端操作系統(tǒng)))上運(yùn)行,你會(huì)怎么設(shè)計(jì)?分別設(shè)計(jì)三套不同
的應(yīng)用?非也,通過(guò)抽象工廠模式屏蔽掉操作系統(tǒng)對(duì)應(yīng)用的影響。三個(gè)不同操作系統(tǒng)上的軟
件功能、應(yīng)用邏輯、UI都應(yīng)該是非常類似的,唯一不同的是調(diào)用不同的工廠方法,由不同的
產(chǎn)品類去處理與操作系統(tǒng)交互的信息
4.2.5 模式擴(kuò)展
簡(jiǎn)單工廠+配置文件解除耦合
可以通過(guò)工廠模式+配置文件的方式解除工廠對(duì)象和產(chǎn)品對(duì)象的耦合。在工廠類中加載配置文件中的全類名,并創(chuàng)建對(duì)象進(jìn)行存儲(chǔ),客戶端如果需要對(duì)象,直接進(jìn)行獲取即可。
第一步:定義配置文件
為了演示方便,我們使用properties文件作為配置文件,名稱為bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee第二步:改進(jìn)工廠類
public class CoffeeFactory {private static Map<String,Coffee> map = new HashMap();static {Properties p = new Properties();InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try {p.load(is);//遍歷Properties集合對(duì)象Set<Object> keys = p.keySet();for (Object key : keys) {//根據(jù)鍵獲取值(全類名)String className = p.getProperty((String) key);//獲取字節(jié)碼對(duì)象Class clazz = Class.forName(className);Coffee obj = (Coffee) clazz.newInstance();map.put((String)key,obj);}} catch (Exception e) {e.printStackTrace();}}public static Coffee createCoffee(String name) {return map.get(name);} }靜態(tài)成員變量用來(lái)存儲(chǔ)創(chuàng)建的對(duì)象(鍵存儲(chǔ)的是名稱,值存儲(chǔ)的是對(duì)應(yīng)的對(duì)象),而讀取配置文件以及創(chuàng)建對(duì)象寫在靜態(tài)代碼塊中,目的就是只需要執(zhí)行一次。
4.2.6 JDK源碼解析-Collection.iterator方法
public class Demo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("令狐沖");list.add("風(fēng)清揚(yáng)");list.add("任我行");//獲取迭代器對(duì)象Iterator<String> it = list.iterator();//使用迭代器遍歷while(it.hasNext()) {String ele = it.next();System.out.println(ele);}} }對(duì)上面的代碼大家應(yīng)該很熟,使用迭代器遍歷集合,獲取集合中的元素。而單列集合獲取迭代器的方法就使用到了工廠方法模式。我們看通過(guò)類圖看看結(jié)構(gòu):
Collection接口是抽象工廠類,ArrayList是具體的工廠類;Iterator接口是抽象商品類,ArrayList類中的Iter內(nèi)部類是具體的商品類。在具體的工廠類中iterator()方法創(chuàng)建具體的商品類的對(duì)象。
另:
? 1,DateForamt類中的getInstance()方法使用的是工廠模式;
? 2,Calendar類中的getInstance()方法使用的是工廠模式;
4.3 原型模式
4.3.1 概述
用一個(gè)已經(jīng)創(chuàng)建的實(shí)例作為原型,通過(guò)復(fù)制該原型對(duì)象來(lái)創(chuàng)建一個(gè)和原型對(duì)象相同的新對(duì)象。
4.3.2 結(jié)構(gòu)
原型模式包含如下角色:
- 抽象原型類:規(guī)定了具體原型對(duì)象必須實(shí)現(xiàn)的的 clone() 方法。
- 具體原型類:實(shí)現(xiàn)抽象原型類的 clone() 方法,它是可被復(fù)制的對(duì)象。
- 訪問(wèn)類:使用具體原型類中的 clone() 方法來(lái)復(fù)制新的對(duì)象。
接口類圖如下:
4.3.3 實(shí)現(xiàn)
原型模式的克隆分為淺克隆和深克隆。
淺克隆:創(chuàng)建一個(gè)新對(duì)象,新對(duì)象的屬性和原來(lái)對(duì)象完全相同,對(duì)于非基本類型屬性,仍指向原有屬性所指向的對(duì)象的內(nèi)存地址。
深克隆:創(chuàng)建一個(gè)新對(duì)象,屬性中引用的其他對(duì)象也會(huì)被克隆,不再指向原有對(duì)象地址。
Java中的Object類中提供了 clone() 方法來(lái)實(shí)現(xiàn)淺克隆。 Cloneable 接口是上面的類圖中的抽象原型類,而實(shí)現(xiàn)了Cloneable接口的子實(shí)現(xiàn)類就是具體的原型類。代碼如下:
Realizetype(具體的原型類):
public class Realizetype implements Cloneable {public Realizetype() {System.out.println("具體的原型對(duì)象創(chuàng)建完成!");}@Overrideprotected Realizetype clone() throws CloneNotSupportedException {System.out.println("具體原型復(fù)制成功!");return (Realizetype) super.clone();} }PrototypeTest(測(cè)試訪問(wèn)類):
public class PrototypeTest {public static void main(String[] args) throws CloneNotSupportedException {Realizetype r1 = new Realizetype();Realizetype r2 = r1.clone();System.out.println("對(duì)象r1和r2是同一個(gè)對(duì)象?" + (r1 == r2));} }4.3.4 案例
用原型模式生成“三好學(xué)生”獎(jiǎng)狀
同一學(xué)校的“三好學(xué)生”獎(jiǎng)狀除了獲獎(jiǎng)人姓名不同,其他都相同,可以使用原型模式復(fù)制多個(gè)“三好學(xué)生”獎(jiǎng)狀出來(lái),然后在修改獎(jiǎng)狀上的名字即可。
類圖如下:
代碼如下:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private String name;public void setName(String name) {this.name = name;}public String getName() {return (this.name);}public void show() {System.out.println(name + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();} }//測(cè)試訪問(wèn)類 public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();c1.setName("張三");//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//將獎(jiǎng)狀的名字修改李四c2.setName("李四");c1.show();c2.show();} }4.3.5 使用場(chǎng)景
- 對(duì)象的創(chuàng)建非常復(fù)雜,可以使用原型模式快捷的創(chuàng)建對(duì)象。
- 性能和安全要求比較高。
4.3.6 擴(kuò)展(深克隆)
將上面的“三好學(xué)生”獎(jiǎng)狀的案例中Citation類的name屬性修改為Student類型的屬性。代碼如下:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu = stu;}void show() {System.out.println(stu.getName() + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();} }//學(xué)生類 public class Student {private String name;private String address;public Student(String name, String address) {this.name = name;this.address = address;}public Student() {}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;} }//測(cè)試類 public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }運(yùn)行結(jié)果為:
說(shuō)明:
? stu對(duì)象和stu1對(duì)象是同一個(gè)對(duì)象,就會(huì)產(chǎn)生將stu1對(duì)象中name屬性值改為“李四”,兩個(gè)Citation(獎(jiǎng)狀)對(duì)象中顯示的都是李四。這就是淺克隆的效果,對(duì)具體原型類(Citation)中的引用類型的屬性進(jìn)行引用的復(fù)制。這種情況需要使用深克隆,而進(jìn)行深克隆需要使用對(duì)象流。代碼如下:
public class CitationTest1 {public static void main(String[] args) throws Exception {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//創(chuàng)建對(duì)象輸出流對(duì)象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));//將c1對(duì)象寫出到文件中oos.writeObject(c1);oos.close();//創(chuàng)建對(duì)象出入流對(duì)象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));//讀取對(duì)象Citation c2 = (Citation) ois.readObject();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }運(yùn)行結(jié)果為:
注意:Citation類和Student類必須實(shí)現(xiàn)Serializable接口,否則會(huì)拋NotSerializableException異常。
另一個(gè)方法:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu = stu;}void show() {System.out.println(stu.getName() + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {Citation clone = (Citation)super.clone();clone.stu= (Student) this.stu.clone();return clone;} //學(xué)生類 public class Student implements Cloneable{private String name;private String address;public Student(String name, String address) {this.name = name;this.address = address;}public Student() {}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;}@Overridepublic Student clone() {try {Student clone = (Student) super.clone();return clone;} catch (CloneNotSupportedException e) {throw new AssertionError();}} }public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }//stu和stu1是同一個(gè)對(duì)象?false4,創(chuàng)建型模式
4.2 工廠模式
4.2.1 概述
需求:設(shè)計(jì)一個(gè)咖啡店點(diǎn)餐系統(tǒng)。
設(shè)計(jì)一個(gè)咖啡類(Coffee),并定義其兩個(gè)子類(美式咖啡【AmericanCoffee】和拿鐵咖啡【LatteCoffee】);再設(shè)計(jì)一個(gè)咖啡店類(CoffeeStore),咖啡店具有點(diǎn)咖啡的功能。
具體類的設(shè)計(jì)如下:
在java中,萬(wàn)物皆對(duì)象,這些對(duì)象都需要?jiǎng)?chuàng)建,如果創(chuàng)建的時(shí)候直接new該對(duì)象,就會(huì)對(duì)該對(duì)象耦合嚴(yán)重,假如我們要更換對(duì)象,所有new對(duì)象的地方都需要修改一遍,這顯然違背了軟件設(shè)計(jì)的開(kāi)閉原則。如果我們使用工廠來(lái)生產(chǎn)對(duì)象,我們就只和工廠打交道就可以了,徹底和對(duì)象解耦,如果要更換對(duì)象,直接在工廠里更換該對(duì)象即可,達(dá)到了與對(duì)象解耦的目的;所以說(shuō),工廠模式最大的優(yōu)點(diǎn)就是:解耦。
在本教程中會(huì)介紹三種工廠的使用
- 簡(jiǎn)單工廠模式(不屬于GOF的23種經(jīng)典設(shè)計(jì)模式)
- 工廠方法模式
- 抽象工廠模式
4.2.2 簡(jiǎn)單工廠模式
簡(jiǎn)單工廠不是一種設(shè)計(jì)模式,反而比較像是一種編程習(xí)慣。
4.2.2.1 結(jié)構(gòu)
簡(jiǎn)單工廠包含如下角色:
- 抽象產(chǎn)品 :定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品 :實(shí)現(xiàn)或者繼承抽象產(chǎn)品的子類
- 具體工廠 :提供了創(chuàng)建產(chǎn)品的方法,調(diào)用者通過(guò)該方法來(lái)獲取產(chǎn)品。
4.2.2.2 實(shí)現(xiàn)
現(xiàn)在使用簡(jiǎn)單工廠對(duì)上面案例進(jìn)行改進(jìn),類圖如下:
工廠類代碼如下:
public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffee;} }工廠(factory)處理創(chuàng)建對(duì)象的細(xì)節(jié),一旦有了SimpleCoffeeFactory,CoffeeStore類中的orderCoffee()就變成此對(duì)象的客戶,后期如果需要Coffee對(duì)象直接從工廠中獲取即可。這樣也就解除了和Coffee實(shí)現(xiàn)類的耦合,同時(shí)又產(chǎn)生了新的耦合,CoffeeStore對(duì)象和SimpleCoffeeFactory工廠對(duì)象的耦合,工廠對(duì)象和商品對(duì)象的耦合。
后期如果再加新品種的咖啡,我們勢(shì)必要需求修改SimpleCoffeeFactory的代碼,違反了開(kāi)閉原則。工廠類的客戶端可能有很多,比如創(chuàng)建美團(tuán)外賣等,這樣只需要修改工廠類的代碼,省去其他的修改操作。
4.2.2.4 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
封裝了創(chuàng)建對(duì)象的過(guò)程,可以通過(guò)參數(shù)直接獲取對(duì)象。把對(duì)象的創(chuàng)建和業(yè)務(wù)邏輯層分開(kāi),這樣以后就避免了修改客戶代碼,如果要實(shí)現(xiàn)新產(chǎn)品直接修改工廠類,而不需要在原代碼中修改,這樣就降低了客戶代碼修改的可能性,更加容易擴(kuò)展。
缺點(diǎn):
增加新產(chǎn)品時(shí)還是需要修改工廠類的代碼,違背了“開(kāi)閉原則”。
4.2.2.3 擴(kuò)展
靜態(tài)工廠
在開(kāi)發(fā)中也有一部分人將工廠類中的創(chuàng)建對(duì)象的功能定義為靜態(tài)的,這個(gè)就是靜態(tài)工廠模式,它也不是23種設(shè)計(jì)模式中的。代碼如下:
public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffe;} }4.2.3 工廠方法模式
針對(duì)上例中的缺點(diǎn),使用工廠方法模式就可以完美的解決,完全遵循開(kāi)閉原則。
4.2.3.1 概念
定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪個(gè)產(chǎn)品類對(duì)象。工廠方法使一個(gè)產(chǎn)品類的實(shí)例化延遲到其工廠的子類。
4.2.3.2 結(jié)構(gòu)
工廠方法模式的主要角色:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,調(diào)用者通過(guò)它訪問(wèn)具體工廠的工廠方法來(lái)創(chuàng)建產(chǎn)品。
- 具體工廠(ConcreteFactory):主要是實(shí)現(xiàn)抽象工廠中的抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能。
- 具體產(chǎn)品(ConcreteProduct):實(shí)現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來(lái)創(chuàng)建,它同具體工廠之間一一對(duì)應(yīng)。
4.2.3.3 實(shí)現(xiàn)
使用工廠方法模式對(duì)上例進(jìn)行改進(jìn),類圖如下:
代碼如下:
抽象工廠:
public interface CoffeeFactory {Coffee createCoffee(); }具體工廠:
public class LatteCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new LatteCoffee();} }public class AmericanCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new AmericanCoffee();} }咖啡店類:
public class CoffeeStore {private CoffeeFactory factory;public CoffeeStore(CoffeeFactory factory) {this.factory = factory;}public Coffee orderCoffee(String type) {Coffee coffee = factory.createCoffee();coffee.addMilk();coffee.addsugar();return coffee;} }從以上的編寫的代碼可以看到,要增加產(chǎn)品類時(shí)也要相應(yīng)地增加工廠類,不需要修改工廠類的代碼了,這樣就解決了簡(jiǎn)單工廠模式的缺點(diǎn)。
工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象。由于使用了多態(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。
4.2.3.4 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 用戶只需要知道具體工廠的名稱就可得到所要的產(chǎn)品,無(wú)須知道產(chǎn)品的具體創(chuàng)建過(guò)程;
- 在系統(tǒng)增加新的產(chǎn)品時(shí)只需要添加具體產(chǎn)品類和對(duì)應(yīng)的具體工廠類,無(wú)須對(duì)原工廠進(jìn)行任何修改,滿足開(kāi)閉原則;
缺點(diǎn):
- 每增加一個(gè)產(chǎn)品就要增加一個(gè)具體產(chǎn)品類和一個(gè)對(duì)應(yīng)的具體工廠類,這增加了系統(tǒng)的復(fù)雜度。
工廠方法模式的優(yōu)點(diǎn)
首先,良好的封裝性,代碼結(jié)構(gòu)清晰。一個(gè)對(duì)象創(chuàng)建是有條件約束的,如一個(gè)調(diào)用者需要一個(gè)具體的產(chǎn)品對(duì)象,只要知道這個(gè)產(chǎn)品的類名(或約束字符串)就可以了,不用知道創(chuàng)建對(duì)象的艱辛過(guò)程,降低模塊間的耦合。其次,工廠方法模式的擴(kuò)展性非常優(yōu)秀。在增加產(chǎn)品類的情況下,只要適當(dāng)?shù)匦薷木唧w的工廠類或擴(kuò)展一個(gè)工廠類,就可以完成“擁抱變化”。工廠類不用任何修改就可完成系統(tǒng)擴(kuò)展。再次,屏蔽產(chǎn)品類。這一特點(diǎn)非常重要,產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者都不需要關(guān)心,它只需要關(guān)心產(chǎn)品的接口,只要接口保持不變,系統(tǒng)中的上層模塊就不要發(fā)生變化。因?yàn)楫a(chǎn)品類的實(shí)例化工作是由工廠類負(fù)責(zé)的,一個(gè)產(chǎn)品對(duì)象具體由哪一個(gè)產(chǎn)品生成是由工廠類決定的。在數(shù)據(jù)庫(kù)開(kāi)發(fā)中,大家應(yīng)該能夠深刻體會(huì)到工廠方法模式的好處:如果使用JDBC連接數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)從MySQL切換到Oracle,需要改動(dòng)的地方就是切換一下驅(qū)動(dòng)名稱(前提條件是SQL語(yǔ)句是標(biāo)準(zhǔn)語(yǔ)句),其他的都不需要修改,這是工廠方法模式靈活性的一個(gè)直接案例。
最后,工廠方法模式是典型的解耦框架。高層模塊值需要知道產(chǎn)品的抽象類,其他的實(shí)現(xiàn)類都不用關(guān)心,符合迪米特法則,我不需要的就不要去交流;也符合依賴倒置原則,只依賴產(chǎn)品類的抽象;當(dāng)然也符合里氏替換原則,使用產(chǎn)品子類替換產(chǎn)品父類,沒(méi)問(wèn)題!
4.2.4 抽象工廠模式
前面介紹的工廠方法模式中考慮的是一類產(chǎn)品的生產(chǎn),如畜牧場(chǎng)只養(yǎng)動(dòng)物、電視機(jī)廠只生產(chǎn)電視機(jī)、傳智播客只培養(yǎng)計(jì)算機(jī)軟件專業(yè)的學(xué)生等。
這些工廠只生產(chǎn)同種類產(chǎn)品,同種類產(chǎn)品稱為同等級(jí)產(chǎn)品,也就是說(shuō):工廠方法模式只考慮生產(chǎn)同等級(jí)的產(chǎn)品,但是在現(xiàn)實(shí)生活中許多工廠是綜合型的工廠,能生產(chǎn)多等級(jí)(種類) 的產(chǎn)品,如電器廠既生產(chǎn)電視機(jī)又生產(chǎn)洗衣機(jī)或空調(diào),大學(xué)既有軟件專業(yè)又有生物專業(yè)等。
本節(jié)要介紹的抽象工廠模式將考慮多等級(jí)產(chǎn)品的生產(chǎn),將同一個(gè)具體工廠所生產(chǎn)的位于不同等級(jí)的一組產(chǎn)品稱為一個(gè)產(chǎn)品族,下圖所示橫軸是產(chǎn)品等級(jí),也就是同一類產(chǎn)品;縱軸是產(chǎn)品族,也就是同一品牌的產(chǎn)品,同一品牌的產(chǎn)品產(chǎn)自同一個(gè)工廠。
4.2.4.1 概念
是一種為訪問(wèn)類提供一個(gè)創(chuàng)建一組相關(guān)或相互依賴對(duì)象的接口,且訪問(wèn)類無(wú)須指定所要產(chǎn)品的具體類就能得到同族的不同等級(jí)的產(chǎn)品的模式結(jié)構(gòu)。
抽象工廠模式是工廠方法模式的升級(jí)版本,工廠方法模式只生產(chǎn)一個(gè)等級(jí)的產(chǎn)品,而抽象工廠模式可生產(chǎn)多個(gè)等級(jí)的產(chǎn)品。
4.2.4.2 結(jié)構(gòu)
抽象工廠模式的主要角色如下:
- 抽象工廠(Abstract Factory):提供了創(chuàng)建產(chǎn)品的接口,它包含多個(gè)創(chuàng)建產(chǎn)品的方法,可以創(chuàng)建多個(gè)不同等級(jí)的產(chǎn)品。
- 具體工廠(Concrete Factory):主要是實(shí)現(xiàn)抽象工廠中的多個(gè)抽象方法,完成具體產(chǎn)品的創(chuàng)建。
- 抽象產(chǎn)品(Product):定義了產(chǎn)品的規(guī)范,描述了產(chǎn)品的主要特性和功能,抽象工廠模式有多個(gè)抽象產(chǎn)品。
- 具體產(chǎn)品(ConcreteProduct):實(shí)現(xiàn)了抽象產(chǎn)品角色所定義的接口,由具體工廠來(lái)創(chuàng)建,它 同具體工廠之間是多對(duì)一的關(guān)系。
4.2.4.2 實(shí)現(xiàn)
現(xiàn)咖啡店業(yè)務(wù)發(fā)生改變,不僅要生產(chǎn)咖啡還要生產(chǎn)甜點(diǎn),如提拉米蘇、抹茶慕斯等,要是按照工廠方法模式,需要定義提拉米蘇類、抹茶慕斯類、提拉米蘇工廠、抹茶慕斯工廠、甜點(diǎn)工廠類,很容易發(fā)生類爆炸情況。其中拿鐵咖啡、美式咖啡是一個(gè)產(chǎn)品等級(jí),都是咖啡;提拉米蘇、抹茶慕斯也是一個(gè)產(chǎn)品等級(jí);拿鐵咖啡和提拉米蘇是同一產(chǎn)品族(也就是都屬于意大利風(fēng)味),美式咖啡和抹茶慕斯是同一產(chǎn)品族(也就是都屬于美式風(fēng)味)。所以這個(gè)案例可以使用抽象工廠模式實(shí)現(xiàn)。類圖如下:
代碼如下:
抽象工廠:
public interface DessertFactory {Coffee createCoffee();Dessert createDessert(); }具體工廠:
//美式甜點(diǎn)工廠 public class AmericanDessertFactory implements DessertFactory {public Coffee createCoffee() {return new AmericanCoffee();}public Dessert createDessert() {return new MatchaMousse();} } //意大利風(fēng)味甜點(diǎn)工廠 public class ItalyDessertFactory implements DessertFactory {public Coffee createCoffee() {return new LatteCoffee();}public Dessert createDessert() {return new Tiramisu();} }如果要加同一個(gè)產(chǎn)品族的話,只需要再加一個(gè)對(duì)應(yīng)的工廠類即可,不需要修改其他的類。
4.2.4.3 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。
缺點(diǎn):
當(dāng)產(chǎn)品族中需要增加一個(gè)新的產(chǎn)品時(shí),所有的工廠類都需要進(jìn)行修改。
4.2.4.4 使用場(chǎng)景
-
當(dāng)需要?jiǎng)?chuàng)建的對(duì)象是一系列相互關(guān)聯(lián)或相互依賴的產(chǎn)品族時(shí),如電器工廠中的電視機(jī)、洗衣機(jī)、空調(diào)等。
-
系統(tǒng)中有多個(gè)產(chǎn)品族,但每次只使用其中的某一族產(chǎn)品。如有人只喜歡穿某一個(gè)品牌的衣服和鞋。
-
系統(tǒng)中提供了產(chǎn)品的類庫(kù),且所有產(chǎn)品的接口相同,客戶端不依賴產(chǎn)品實(shí)例的創(chuàng)建細(xì)節(jié)和內(nèi)部結(jié)構(gòu)。
如:輸入法換皮膚,一整套一起換。生成不同操作系統(tǒng)的程序。
最佳實(shí)踐
一個(gè)模式在什么情況下才能夠使用,是很多讀者比較困惑的地方。抽象工廠模式是一個(gè)
簡(jiǎn)單的模式,使用的場(chǎng)景非常多,大家在軟件產(chǎn)品開(kāi)發(fā)過(guò)程中,涉及不同操作系統(tǒng)的時(shí)候,
都可以考慮使用抽象工廠模式,例如一個(gè)應(yīng)用,需要在三個(gè)不同平臺(tái)(Windows、Linux、
Android(Google發(fā)布的智能終端操作系統(tǒng)))上運(yùn)行,你會(huì)怎么設(shè)計(jì)?分別設(shè)計(jì)三套不同
的應(yīng)用?非也,通過(guò)抽象工廠模式屏蔽掉操作系統(tǒng)對(duì)應(yīng)用的影響。三個(gè)不同操作系統(tǒng)上的軟
件功能、應(yīng)用邏輯、UI都應(yīng)該是非常類似的,唯一不同的是調(diào)用不同的工廠方法,由不同的
產(chǎn)品類去處理與操作系統(tǒng)交互的信息
4.2.5 模式擴(kuò)展
簡(jiǎn)單工廠+配置文件解除耦合
可以通過(guò)工廠模式+配置文件的方式解除工廠對(duì)象和產(chǎn)品對(duì)象的耦合。在工廠類中加載配置文件中的全類名,并創(chuàng)建對(duì)象進(jìn)行存儲(chǔ),客戶端如果需要對(duì)象,直接進(jìn)行獲取即可。
第一步:定義配置文件
為了演示方便,我們使用properties文件作為配置文件,名稱為bean.properties
american=com.itheima.pattern.factory.config_factory.AmericanCoffee latte=com.itheima.pattern.factory.config_factory.LatteCoffee第二步:改進(jìn)工廠類
public class CoffeeFactory {private static Map<String,Coffee> map = new HashMap();static {Properties p = new Properties();InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try {p.load(is);//遍歷Properties集合對(duì)象Set<Object> keys = p.keySet();for (Object key : keys) {//根據(jù)鍵獲取值(全類名)String className = p.getProperty((String) key);//獲取字節(jié)碼對(duì)象Class clazz = Class.forName(className);Coffee obj = (Coffee) clazz.newInstance();map.put((String)key,obj);}} catch (Exception e) {e.printStackTrace();}}public static Coffee createCoffee(String name) {return map.get(name);} }靜態(tài)成員變量用來(lái)存儲(chǔ)創(chuàng)建的對(duì)象(鍵存儲(chǔ)的是名稱,值存儲(chǔ)的是對(duì)應(yīng)的對(duì)象),而讀取配置文件以及創(chuàng)建對(duì)象寫在靜態(tài)代碼塊中,目的就是只需要執(zhí)行一次。
4.2.6 JDK源碼解析-Collection.iterator方法
public class Demo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("令狐沖");list.add("風(fēng)清揚(yáng)");list.add("任我行");//獲取迭代器對(duì)象Iterator<String> it = list.iterator();//使用迭代器遍歷while(it.hasNext()) {String ele = it.next();System.out.println(ele);}} }對(duì)上面的代碼大家應(yīng)該很熟,使用迭代器遍歷集合,獲取集合中的元素。而單列集合獲取迭代器的方法就使用到了工廠方法模式。我們看通過(guò)類圖看看結(jié)構(gòu):
Collection接口是抽象工廠類,ArrayList是具體的工廠類;Iterator接口是抽象商品類,ArrayList類中的Iter內(nèi)部類是具體的商品類。在具體的工廠類中iterator()方法創(chuàng)建具體的商品類的對(duì)象。
另:
? 1,DateForamt類中的getInstance()方法使用的是工廠模式;
? 2,Calendar類中的getInstance()方法使用的是工廠模式;
4.3 原型模式
4.3.1 概述
用一個(gè)已經(jīng)創(chuàng)建的實(shí)例作為原型,通過(guò)復(fù)制該原型對(duì)象來(lái)創(chuàng)建一個(gè)和原型對(duì)象相同的新對(duì)象。
4.3.2 結(jié)構(gòu)
原型模式包含如下角色:
- 抽象原型類:規(guī)定了具體原型對(duì)象必須實(shí)現(xiàn)的的 clone() 方法。
- 具體原型類:實(shí)現(xiàn)抽象原型類的 clone() 方法,它是可被復(fù)制的對(duì)象。
- 訪問(wèn)類:使用具體原型類中的 clone() 方法來(lái)復(fù)制新的對(duì)象。
接口類圖如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-ry6LSSoN-1666867748031)(http://bijioss.donggei.top/%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F.png)]
4.3.3 實(shí)現(xiàn)
原型模式的克隆分為淺克隆和深克隆。
淺克隆:創(chuàng)建一個(gè)新對(duì)象,新對(duì)象的屬性和原來(lái)對(duì)象完全相同,對(duì)于非基本類型屬性,仍指向原有屬性所指向的對(duì)象的內(nèi)存地址。
深克隆:創(chuàng)建一個(gè)新對(duì)象,屬性中引用的其他對(duì)象也會(huì)被克隆,不再指向原有對(duì)象地址。
Java中的Object類中提供了 clone() 方法來(lái)實(shí)現(xiàn)淺克隆。 Cloneable 接口是上面的類圖中的抽象原型類,而實(shí)現(xiàn)了Cloneable接口的子實(shí)現(xiàn)類就是具體的原型類。代碼如下:
Realizetype(具體的原型類):
public class Realizetype implements Cloneable {public Realizetype() {System.out.println("具體的原型對(duì)象創(chuàng)建完成!");}@Overrideprotected Realizetype clone() throws CloneNotSupportedException {System.out.println("具體原型復(fù)制成功!");return (Realizetype) super.clone();} }PrototypeTest(測(cè)試訪問(wèn)類):
public class PrototypeTest {public static void main(String[] args) throws CloneNotSupportedException {Realizetype r1 = new Realizetype();Realizetype r2 = r1.clone();System.out.println("對(duì)象r1和r2是同一個(gè)對(duì)象?" + (r1 == r2));} }4.3.4 案例
用原型模式生成“三好學(xué)生”獎(jiǎng)狀
同一學(xué)校的“三好學(xué)生”獎(jiǎng)狀除了獲獎(jiǎng)人姓名不同,其他都相同,可以使用原型模式復(fù)制多個(gè)“三好學(xué)生”獎(jiǎng)狀出來(lái),然后在修改獎(jiǎng)狀上的名字即可。
類圖如下:
代碼如下:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private String name;public void setName(String name) {this.name = name;}public String getName() {return (this.name);}public void show() {System.out.println(name + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();} }//測(cè)試訪問(wèn)類 public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();c1.setName("張三");//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//將獎(jiǎng)狀的名字修改李四c2.setName("李四");c1.show();c2.show();} }4.3.5 使用場(chǎng)景
- 對(duì)象的創(chuàng)建非常復(fù)雜,可以使用原型模式快捷的創(chuàng)建對(duì)象。
- 性能和安全要求比較高。
4.3.6 擴(kuò)展(深克隆)
將上面的“三好學(xué)生”獎(jiǎng)狀的案例中Citation類的name屬性修改為Student類型的屬性。代碼如下:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu = stu;}void show() {System.out.println(stu.getName() + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();} }//學(xué)生類 public class Student {private String name;private String address;public Student(String name, String address) {this.name = name;this.address = address;}public Student() {}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;} }//測(cè)試類 public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }運(yùn)行結(jié)果為:
說(shuō)明:
? stu對(duì)象和stu1對(duì)象是同一個(gè)對(duì)象,就會(huì)產(chǎn)生將stu1對(duì)象中name屬性值改為“李四”,兩個(gè)Citation(獎(jiǎng)狀)對(duì)象中顯示的都是李四。這就是淺克隆的效果,對(duì)具體原型類(Citation)中的引用類型的屬性進(jìn)行引用的復(fù)制。這種情況需要使用深克隆,而進(jìn)行深克隆需要使用對(duì)象流。代碼如下:
public class CitationTest1 {public static void main(String[] args) throws Exception {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//創(chuàng)建對(duì)象輸出流對(duì)象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));//將c1對(duì)象寫出到文件中oos.writeObject(c1);oos.close();//創(chuàng)建對(duì)象出入流對(duì)象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));//讀取對(duì)象Citation c2 = (Citation) ois.readObject();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }運(yùn)行結(jié)果為:
注意:Citation類和Student類必須實(shí)現(xiàn)Serializable接口,否則會(huì)拋NotSerializableException異常。
另一個(gè)方法:
//獎(jiǎng)狀類 public class Citation implements Cloneable {private Student stu;public Student getStu() {return stu;}public void setStu(Student stu) {this.stu = stu;}void show() {System.out.println(stu.getName() + "同學(xué):在2020學(xué)年第一學(xué)期中表現(xiàn)優(yōu)秀,被評(píng)為三好學(xué)生。特發(fā)此狀!");}@Overridepublic Citation clone() throws CloneNotSupportedException {Citation clone = (Citation)super.clone();clone.stu= (Student) this.stu.clone();return clone;} //學(xué)生類 public class Student implements Cloneable{private String name;private String address;public Student(String name, String address) {this.name = name;this.address = address;}public Student() {}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;}@Overridepublic Student clone() {try {Student clone = (Student) super.clone();return clone;} catch (CloneNotSupportedException e) {throw new AssertionError();}} }public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {Citation c1 = new Citation();Student stu = new Student("張三", "西安");c1.setStu(stu);//復(fù)制獎(jiǎng)狀Citation c2 = c1.clone();//獲取c2獎(jiǎng)狀所屬學(xué)生對(duì)象Student stu1 = c2.getStu();stu1.setName("李四");//判斷stu對(duì)象和stu1對(duì)象是否是同一個(gè)對(duì)象System.out.println("stu和stu1是同一個(gè)對(duì)象?" + (stu == stu1));c1.show();c2.show();} }//stu和stu1是同一個(gè)對(duì)象?false4.5 建造者模式
4.4.1 概述
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。
- 分離了部件的構(gòu)造(由Builder來(lái)負(fù)責(zé))和裝配(由Director負(fù)責(zé))。 從而可以構(gòu)造出復(fù)雜的對(duì)象。這個(gè)模式適用于:某個(gè)對(duì)象的構(gòu)建過(guò)程復(fù)雜的情況。
- 由于實(shí)現(xiàn)了構(gòu)建和裝配的解耦。不同的構(gòu)建器,相同的裝配,也可以做出不同的對(duì)象;相同的構(gòu)建器,不同的裝配順序也可以做出不同的對(duì)象。也就是實(shí)現(xiàn)了構(gòu)建算法、裝配算法的解耦,實(shí)現(xiàn)了更好的復(fù)用。
- 建造者模式可以將部件和其組裝過(guò)程分開(kāi),一步一步創(chuàng)建一個(gè)復(fù)雜的對(duì)象。用戶只需要指定復(fù)雜對(duì)象的類型就可以得到該對(duì)象,而無(wú)須知道其內(nèi)部的具體構(gòu)造細(xì)節(jié)。
4.4.2 結(jié)構(gòu)
建造者(Builder)模式包含如下角色:
-
抽象建造者類(Builder):這個(gè)接口規(guī)定要實(shí)現(xiàn)復(fù)雜對(duì)象的那些部分的創(chuàng)建,并不涉及具體的部件對(duì)象的創(chuàng)建。
-
具體建造者類(ConcreteBuilder):實(shí)現(xiàn) Builder 接口,完成復(fù)雜產(chǎn)品的各個(gè)部件的具體創(chuàng)建方法。在構(gòu)造過(guò)程完成后,提供產(chǎn)品的實(shí)例。
-
產(chǎn)品類(Product):要?jiǎng)?chuàng)建的復(fù)雜對(duì)象。
-
指揮者類(Director):調(diào)用具體建造者來(lái)創(chuàng)建復(fù)雜對(duì)象的各個(gè)部分,在指導(dǎo)者中不涉及具體產(chǎn)品的信息,只負(fù)責(zé)保證對(duì)象各部分完整創(chuàng)建或按某種順序創(chuàng)建。
類圖如下:
4.4.3 實(shí)例
創(chuàng)建共享單車
生產(chǎn)自行車是一個(gè)復(fù)雜的過(guò)程,它包含了車架,車座等組件的生產(chǎn)。而車架又有碳纖維,鋁合金等材質(zhì)的,車座有橡膠,真皮等材質(zhì)。對(duì)于自行車的生產(chǎn)就可以使用建造者模式。
這里Bike是產(chǎn)品,包含車架,車座等組件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具體的建造者;Director是指揮者。類圖如下:
具體的代碼如下:
//自行車類 public class Bike {private String frame;private String seat;public String getFrame() {return frame;}public void setFrame(String frame) {this.frame = frame;}public String getSeat() {return seat;}public void setSeat(String seat) {this.seat = seat;} }// 抽象 builder 類 public abstract class Builder {protected Bike mBike = new Bike();public abstract void buildFrame();public abstract void buildSeat();public abstract Bike createBike(); }//摩拜單車Builder類 public class MobikeBuilder extends Builder {@Overridepublic void buildFrame() {mBike.setFrame("鋁合金車架");}@Overridepublic void buildSeat() {mBike.setSeat("真皮車座");}@Overridepublic Bike createBike() {return mBike;} }//ofo單車Builder類 public class OfoBuilder extends Builder {@Overridepublic void buildFrame() {mBike.setFrame("碳纖維車架");}@Overridepublic void buildSeat() {mBike.setSeat("橡膠車座");}@Overridepublic Bike createBike() {return mBike;} }//指揮者類 public class Director {private Builder mBuilder;public Director(Builder builder) {mBuilder = builder;}public Bike construct() {mBuilder.buildFrame();mBuilder.buildSeat();return mBuilder.createBike();} }//測(cè)試類 public class Client {public static void main(String[] args) {showBike(new OfoBuilder());showBike(new MobikeBuilder());}private static void showBike(Builder builder) {Director director = new Director(builder);Bike bike = director.construct();System.out.println(bike.getFrame());System.out.println(bike.getSeat());} }注意:
上面示例是 Builder模式的常規(guī)用法,指揮者類 Director 在建造者模式中具有很重要的作用,它用于指導(dǎo)具體構(gòu)建者如何構(gòu)建產(chǎn)品,控制調(diào)用先后次序,并向調(diào)用者返回完整的產(chǎn)品類,但是有些情況下需要簡(jiǎn)化系統(tǒng)結(jié)構(gòu),可以把指揮者類和抽象建造者進(jìn)行結(jié)合
// 抽象 builder 類 public abstract class Builder {protected Bike mBike = new Bike();public abstract void buildFrame();public abstract void buildSeat();public abstract Bike createBike();public Bike construct() {this.buildFrame();this.BuildSeat();return this.createBike();} }說(shuō)明:
這樣做確實(shí)簡(jiǎn)化了系統(tǒng)結(jié)構(gòu),但同時(shí)也加重了抽象建造者類的職責(zé),也不是太符合單一職責(zé)原則,如果construct() 過(guò)于復(fù)雜,建議還是封裝到 Director 中。
4.4.4 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 建造者模式的封裝性很好。使用建造者模式可以有效的封裝變化,在使用建造者模式的場(chǎng)景中,一般產(chǎn)品類和建造者類是比較穩(wěn)定的,因此,將主要的業(yè)務(wù)邏輯封裝在指揮者類中對(duì)整體而言可以取得比較好的穩(wěn)定性。
- 在建造者模式中,客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié),將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過(guò)程解耦,使得相同的創(chuàng)建過(guò)程可以創(chuàng)建不同的產(chǎn)品對(duì)象。
- 可以更加精細(xì)地控制產(chǎn)品的創(chuàng)建過(guò)程 。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中,使得創(chuàng)建過(guò)程更加清晰,也更方便使用程序來(lái)控制創(chuàng)建過(guò)程。
- 建造者模式很容易進(jìn)行擴(kuò)展。如果有新的需求,通過(guò)實(shí)現(xiàn)一個(gè)新的建造者類就可以完成,基本上不用修改之前已經(jīng)測(cè)試通過(guò)的代碼,因此也就不會(huì)對(duì)原有功能引入風(fēng)險(xiǎn)。符合開(kāi)閉原則。
缺點(diǎn):
造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn),其組成部分相似,如果產(chǎn)品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。
建造者模式關(guān)注的是零件類型和裝配工藝(順序),這是它與工廠方法模式最大不同的。地方,雖然同為創(chuàng)建類模式,但是注重點(diǎn)不同。
4.4.5 使用場(chǎng)景
建造者(Builder)模式創(chuàng)建的是復(fù)雜對(duì)象,其產(chǎn)品的各個(gè)部分經(jīng)常面臨著劇烈的變化,但將它們組合在一起的算法卻相對(duì)穩(wěn)定,所以它通常在以下場(chǎng)合使用。
- 創(chuàng)建的對(duì)象較復(fù)雜,由多個(gè)部件構(gòu)成,各部件面臨著復(fù)雜的變化,但構(gòu)件間的建造順序是穩(wěn)定的。
- 創(chuàng)建復(fù)雜對(duì)象的算法獨(dú)立于該對(duì)象的組成部分以及它們的裝配方式,即產(chǎn)品的構(gòu)建過(guò)程和最終的表示是獨(dú)立的。
4.4.6 模式擴(kuò)展
建造者模式除了上面的用途外,在開(kāi)發(fā)中還有一個(gè)常用的使用方式,就是當(dāng)一個(gè)類構(gòu)造器需要傳入很多參數(shù)時(shí),如果創(chuàng)建這個(gè)類的實(shí)例,代碼可讀性會(huì)非常差,而且很容易引入錯(cuò)誤,此時(shí)就可以利用建造者模式進(jìn)行重構(gòu)。
重構(gòu)前代碼如下:
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;public Phone(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;}@Overridepublic String toString() {return "Phone{" +"cpu='" + cpu + '\'' +", screen='" + screen + '\'' +", memory='" + memory + '\'' +", mainboard='" + mainboard + '\'' +'}';} }public class Client {public static void main(String[] args) {//構(gòu)建Phone對(duì)象Phone phone = new Phone("intel","三星屏幕","金士頓","華碩");System.out.println(phone);} }上面在客戶端代碼中構(gòu)建Phone對(duì)象,傳遞了四個(gè)參數(shù),如果參數(shù)更多呢?代碼的可讀性及使用的成本就是比較高。
重構(gòu)后代碼:
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;private Phone(Builder builder) {cpu = builder.cpu;screen = builder.screen;memory = builder.memory;mainboard = builder.mainboard;}public static final class Builder {private String cpu;private String screen;private String memory;private String mainboard;public Builder() {}public Builder cpu(String val) {cpu = val;return this;}public Builder screen(String val) {screen = val;return this;}public Builder memory(String val) {memory = val;return this;}public Builder mainboard(String val) {mainboard = val;return this;}public Phone build() {return new Phone(this);}}@Overridepublic String toString() {return "Phone{" +"cpu='" + cpu + '\'' +", screen='" + screen + '\'' +", memory='" + memory + '\'' +", mainboard='" + mainboard + '\'' +'}';} }public class Client {public static void main(String[] args) {Phone phone = new Phone.Builder().cpu("intel").mainboard("華碩").memory("金士頓").screen("三星").build();System.out.println(phone);}//把構(gòu)建的順序交給了用戶!! }重構(gòu)后的代碼在使用起來(lái)更方便,某種程度上也可以提高開(kāi)發(fā)效率。從軟件設(shè)計(jì)上,對(duì)程序員的要求比較高。
4.6 創(chuàng)建者模式對(duì)比
4.6.1 工廠方法模式VS建造者模式
工廠方法模式注重的是整體對(duì)象的創(chuàng)建方式;而建造者模式注重的是部件構(gòu)建的過(guò)程,意在通過(guò)一步一步地精確構(gòu)造創(chuàng)建出一個(gè)復(fù)雜的對(duì)象。
我們舉個(gè)簡(jiǎn)單例子來(lái)說(shuō)明兩者的差異,如要制造一個(gè)超人,如果使用工廠方法模式,直接產(chǎn)生出來(lái)的就是一個(gè)力大無(wú)窮、能夠飛翔、內(nèi)褲外穿的超人;而如果使用建造者模式,則需要組裝手、頭、腳、軀干等部分,然后再把內(nèi)褲外穿,于是一個(gè)超人就誕生了。
4.6.2 抽象工廠模 式VS建造者模式
抽象工廠模式實(shí)現(xiàn)對(duì)產(chǎn)品家族的創(chuàng)建,一個(gè)產(chǎn)品家族是這樣的一系列產(chǎn)品:具有不同分類維度的產(chǎn)品組合,采用抽象工廠模式則是不需要關(guān)心構(gòu)建過(guò)程,只關(guān)心什么產(chǎn)品由什么工廠生產(chǎn)即可。
建造者模式則是要求按照指定的藍(lán)圖建造產(chǎn)品,它的主要目的是通過(guò)組裝零配件而產(chǎn)生一個(gè)新產(chǎn)品。
如果將抽象工廠模式看成汽車配件生產(chǎn)工廠,生產(chǎn)一個(gè)產(chǎn)品族的產(chǎn)品,那么建造者模式就是一個(gè)汽車組裝工廠,通過(guò)對(duì)部件的組裝可以返回一輛完整的汽車。
總結(jié)
- 上一篇: 报错集-------docker中删除镜
- 下一篇: Mesos | 1.3.2 webui