php網(wǎng)站后臺(tái)模板推廣app最快的方法
聲明:僅為個(gè)人學(xué)習(xí)總結(jié),還請(qǐng)批判性查看,如有不同觀點(diǎn),歡迎交流。
摘要
《Head First設(shè)計(jì)模式》第5章筆記:結(jié)合示例應(yīng)用和代碼,介紹單例模式,包括遇到的問題、采用的解決方案、以及達(dá)到的效果。
目錄
- 摘要
- 1 示例應(yīng)用
- 2 引入設(shè)計(jì)模式
- 2.1 私有構(gòu)造方法
- 2.2 靜態(tài)方法
- 2.3 靜態(tài)變量
- 2.4 經(jīng)典單例實(shí)現(xiàn)
- 2.5 單例模式定義
- 2.6 第1版改進(jìn)
- 3 遇到問題
- 3.1 多線程方案1:同步方法
- 3.2 多線程方案2:急切實(shí)例化
- 3.3 多線程方案3:雙重檢查鎖定
- 3.4 其它注意事項(xiàng)
- 3.5 使用枚舉
- 4 示例代碼
- 4.1 Java 示例
- 4.2 C++11 示例
- 5 設(shè)計(jì)工具箱
- 5.1 OO 基礎(chǔ)
- 5.2 OO 原則
- 5.3 OO 模式
- 參考
1 示例應(yīng)用
示例應(yīng)用是巧克力工廠的鍋爐控制系統(tǒng)。巧克力鍋爐將巧克力和牛奶混合,加熱至沸騰,然后將它們送到制作巧克力棒的下一階段。
鍋爐的狀態(tài)包括 boolean empty
(是否為空)和 boolean boiled
(是否沸騰),相應(yīng)的狀態(tài)轉(zhuǎn)換情況如下:
下面是巧克力鍋爐的定義,在執(zhí)行 fill()
、boil()
、drain()
操作時(shí),都進(jìn)行了嚴(yán)格的狀態(tài)判斷,防止出現(xiàn)鍋爐空燒、排出未煮沸巧克力等糟糕情況。
public class ChocolateBoiler {private boolean empty;private boolean boiled;public ChocolateBoiler() {empty = true;boiled = false;}public void fill() {if (isEmpty()) { // 狀態(tài)為空時(shí),才可以執(zhí)行操作System.out.println("向鍋爐中加滿牛奶和巧克力");empty = false;boiled = false;}}public void drain() {if (!isEmpty() && isBoiled()) { // 狀態(tài)為滿并且沸騰時(shí),才可以執(zhí)行操作System.out.println("從鍋爐中排出牛奶和巧克力");empty = true;boiled = false;}}public void boil() {if (!isEmpty() && !isBoiled()) { // 狀態(tài)為滿并且未沸騰時(shí),才可以執(zhí)行操作System.out.println("將鍋爐中的牛奶和巧克力煮沸");boiled = true;}}public boolean isEmpty() { return empty; }public boolean isBoiled() { return boiled; }
}
除了狀態(tài)監(jiān)控,為保證系統(tǒng)正常運(yùn)行,還要避免為一臺(tái)鍋爐創(chuàng)建多個(gè) ChocolateBoiler
實(shí)例。否則,由它們共同操作鍋爐,情況也會(huì)很糟糕。
2 引入設(shè)計(jì)模式
接下來,我們嘗試通過設(shè)計(jì)模式來確保一個(gè)類 ChocolateBoiler
只能創(chuàng)建單一實(shí)例(單例)。
顯然,通過 new ChocolateBoiler()
可以創(chuàng)建一個(gè)實(shí)例;但是,再次執(zhí)行 new ChocolateBoiler()
還會(huì)創(chuàng)建另一個(gè)實(shí)例。
所以,我們的目標(biāo)是讓 new ChocolateBoiler()
只能被執(zhí)行一次。
要達(dá)成目標(biāo),可以分為下面 3 個(gè)步驟。
2.1 私有構(gòu)造方法
以 MyClass
類為例。
思考題
下面哪個(gè)或哪些選項(xiàng)可以將 MyClass 類實(shí)例化?【答案在第 20 行】public class MyClass {// ...private MyClass() {}
}A. MyClass 的包外類
B. MyClass 的同包類
C. MyClass 的子類
D. MyClass 的內(nèi)部代碼
E. 沒有任何辦法答案:D
解析:一個(gè)類的 private 成員(包括構(gòu)造方法)只能從“它所在類的內(nèi)部”訪問。
達(dá)成目標(biāo)的第1步:定義私有構(gòu)造方法,阻止外部類直接執(zhí)行 new MyClass()
創(chuàng)建實(shí)例。
2.2 靜態(tài)方法
類的靜態(tài)成員(靜態(tài)方法和靜態(tài)變量),屬于類本身,而不屬于類的某個(gè)實(shí)例。
通過在類的內(nèi)部定義 getInstance()
方法,可以訪問類的私有構(gòu)造方法,創(chuàng)建實(shí)例。
public class MyClass {// ...private MyClass() {}public static MyClass getInstance() {return new MyClass();}
}
通過將 getInstance()
聲明為 static
靜態(tài)方法,能夠在不創(chuàng)建任何實(shí)例的情況下,直接使用類名訪問 getInstance()
。
MyClass obj = MyClass.getInstance();
達(dá)成目標(biāo)的第2步:定義靜態(tài)方法,由類自身執(zhí)行 new MyClass()
創(chuàng)建實(shí)例,并對(duì)外提供獲取實(shí)例的統(tǒng)一接口。
2.3 靜態(tài)變量
靜態(tài)變量屬于類本身,用于存儲(chǔ)“類級(jí)別”的狀態(tài)或共享數(shù)據(jù)。由于它與類的任何實(shí)例都無關(guān),所以可以用來控制實(shí)例的創(chuàng)建。
通過在類的內(nèi)部定義靜態(tài)變量 uniqueInstance
,控制 MyClass
只能創(chuàng)建單一實(shí)例(單例)。
public class MyClass {private static MyClass uniqueInstance; // 在類加載時(shí)被初始化為 nullprivate MyClass() {}public static MyClass getInstance() {if (uniqueInstance == null) { // 在還沒有創(chuàng)建任何實(shí)例的情況下,可以創(chuàng)建實(shí)例,確保實(shí)例的唯一性uniqueInstance = new MyClass(); // 使用 uniqueInstance 引用該實(shí)例}return uniqueInstance; // 返回已經(jīng)創(chuàng)建的實(shí)例}
}
達(dá)成目標(biāo)的第3步:定義靜態(tài)變量,引用類的唯一實(shí)例,限制 new MyClass()
只能被執(zhí)行一次。
2.4 經(jīng)典單例實(shí)現(xiàn)
我們使用一個(gè)私有構(gòu)造方法、一個(gè)靜態(tài)方法、一個(gè)靜態(tài)變量,實(shí)現(xiàn)了經(jīng)典的單例模式。
public class Singleton {// 一個(gè)靜態(tài)變量(私有),引用 Singleton 的唯一實(shí)例private static Singleton uniqueInstance;// 在這里添加其它有用的變量// 將構(gòu)造方法聲明為私有的,只有 Singleton 可以實(shí)例化這個(gè)類private Singleton() {// ...}// 一個(gè)靜態(tài)方法(公共),用于創(chuàng)建唯一的 Singleton 實(shí)例,并將其返回public static Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}// 在這里添加其它有用的方法// 當(dāng)然,Singleton 是一個(gè)正常的類,會(huì)有一些實(shí)現(xiàn)相應(yīng)功能的變量和方法
}
在單例模式中:
- 由一個(gè)類來管理自己的唯一實(shí)例,要想訪問實(shí)例,只能通過該類;
- 在需要訪問實(shí)例時(shí),只需要調(diào)用該類提供的靜態(tài)方法(即,該實(shí)例的全局訪問點(diǎn))。
2.5 單例模式定義
單例模式(Singleton Pattern)
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。
Ensure a class only has one instance, and provide a global point of access to it.
- 私有(或保護(hù))構(gòu)造函數(shù)
Singleton()
,確保外部無法直接實(shí)例化; - 私有(或保護(hù))靜態(tài)成員變量
uniqueInstance
,引用Singleton
的唯一實(shí)例; - 公共靜態(tài)成員方法
getInstance()
,提供訪問Singleton
唯一實(shí)例的全局接口; - 業(yè)務(wù)相關(guān)的數(shù)據(jù)
usefulSingletonData
和方法usefulSingletonMethod()
,用于實(shí)現(xiàn)類的具體功能。
單例模式的優(yōu)點(diǎn):
- 控制實(shí)例訪問
通過getInstance()
方法,可以嚴(yán)格控制實(shí)例的訪問時(shí)機(jī)和訪問方式。 - 相比全局變量的優(yōu)勢(shì)
- 實(shí)例唯一性:單例可以保證實(shí)例唯一,而全局變量無法保證;
- 延遲初始化:單例可以根據(jù)需要?jiǎng)?chuàng)建實(shí)例,而全局變量在程序啟動(dòng)時(shí)就會(huì)創(chuàng)建(無論是否會(huì)用到);
- 命名空間占用:單例的實(shí)例被封裝在類內(nèi)部,而全局變量直接占用全局命名空間(命名空間污染)。
- 實(shí)例數(shù)量可變
通過修改 2.3 靜態(tài)變量 的實(shí)現(xiàn),也可以讓Singleton
類管理自己的多個(gè)實(shí)例。
延伸閱讀:《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》 3.5 Singleton(單件)— 對(duì)象創(chuàng)建型模式 [P96-102]
2.6 第1版改進(jìn)
采用單例模式,已經(jīng)實(shí)現(xiàn)讓一個(gè)類只能創(chuàng)建單一實(shí)例。下面是改進(jìn)后的 ChocolateBoiler
類:
public class ChocolateBoiler {private boolean empty;private boolean boiled;// 增加靜態(tài)變量、靜態(tài)方法,修改構(gòu)造方法為私有private static ChocolateBoiler uniqueInstance;private ChocolateBoiler() {empty = true;boiled = false;}public static ChocolateBoiler getInstance() {if (uniqueInstance == null) {uniqueInstance = new ChocolateBoiler();}return uniqueInstance;}// 后面的代碼沒有變化public void fill() {if (isEmpty()) { // 狀態(tài)為空時(shí),才可以執(zhí)行操作System.out.println("向鍋爐中加滿牛奶和巧克力");empty = false;boiled = false;}}public void drain() {if (!isEmpty() && isBoiled()) { // 狀態(tài)為滿并且沸騰時(shí),才可以執(zhí)行操作System.out.println("從鍋爐中排出牛奶和巧克力");empty = true;boiled = false;}}public void boil() {if (!isEmpty() && !isBoiled()) { // 狀態(tài)為滿并且未沸騰時(shí),才可以執(zhí)行操作System.out.println("將鍋爐中的牛奶和巧克力煮沸");boiled = true;}}public boolean isEmpty() { return empty; }public boolean isBoiled() { return boiled; }
}
3 遇到問題
糟糕!在使用多線程對(duì)巧克力鍋爐控制器進(jìn)行優(yōu)化后,鍋爐發(fā)生了溢出!
我們來查找一下問題的原因。
下面是鍋爐控制器 BoilerController
中的相關(guān)代碼:
ChocolateBoiler boiler = ChocolateBoiler.getInstance();
boiler.fill(); // 加滿
boiler.boil(); // 煮沸
boiler.drain(); // 排出
現(xiàn)在有兩個(gè)線程都需要執(zhí)行上述代碼:
- 如果它們引用相同的
ChocolateBoiler
實(shí)例,就會(huì)共享一致的狀態(tài),并通過對(duì)狀態(tài)的嚴(yán)格監(jiān)控,讓鍋爐運(yùn)轉(zhuǎn)良好;(此處忽略數(shù)據(jù)競(jìng)爭(zhēng)) - 如果它們引用不同的
ChocolateBoiler
實(shí)例,就會(huì)擁有各自的狀態(tài),如果一個(gè)實(shí)例已經(jīng)加滿鍋爐,而另一個(gè)實(shí)例還是空置狀態(tài),并繼續(xù)加入原料,就會(huì)導(dǎo)致鍋爐溢出。
可是,ChocolateBoiler
已經(jīng)定義為單例,這兩個(gè)線程還會(huì)引用不同的實(shí)例嗎?
我們來仔細(xì)分析一下 getInstance()
的定義,以及它可能的執(zhí)行時(shí)序。
public static ChocolateBoiler getInstance() {if (uniqueInstance == null) {uniqueInstance = new ChocolateBoiler();}return uniqueInstance;
}
時(shí)序 | 線程1 | 線程2 | uniqueInstance 值 |
---|---|---|---|
1 | ChocolateBoiler.getInstance() | null | |
2 | if (uniqueInstance == null) | null | |
3 | 掛起 | ChocolateBoiler.getInstance() | null |
4 | if (uniqueInstance == null) | null | |
5 | uniqueInstance = new ChocolateBoiler(); | object1 | |
6 | return uniqueInstance; | object1 | |
7 | uniqueInstance = new ChocolateBoiler(); | object2 | |
8 | return uniqueInstance; | object2 |
在多線程環(huán)境下,果然有可能創(chuàng)建兩個(gè) ChocolateBoiler
實(shí)例 object1
和 object2
。
為了確保實(shí)例的創(chuàng)建是線程安全的,我們有多種可選方案,包括同步方法、急切實(shí)例化、雙重檢查鎖定等。
3.1 多線程方案1:同步方法
只要把 getInstance()
變成同步方法(添加 synchronized
關(guān)鍵字),就可以保證實(shí)例創(chuàng)建的線程安全性。
Java 概念:內(nèi)在鎖(intrinsic lock)
每個(gè) Java 對(duì)象都有一個(gè)內(nèi)在鎖,獲得對(duì)象的內(nèi)在鎖就能夠獨(dú)占該對(duì)象的訪問權(quán),試圖訪問被鎖定對(duì)象的線程將被阻塞,直到持有該鎖的線程釋放鎖。使用synchronized
關(guān)鍵字可以獲得對(duì)象的內(nèi)在鎖。
Java 概念:方法同步(Method Synchronization)
- 當(dāng)一個(gè)線程調(diào)用一個(gè)對(duì)象的非靜態(tài)
synchronized
方法時(shí),它會(huì)在方法執(zhí)行之前,自動(dòng)嘗試獲得該對(duì)象的內(nèi)在鎖;在方法返回之前,線程一直持有鎖。- 一旦某個(gè)線程鎖定了某個(gè)對(duì)象,其他線程就不能執(zhí)行同一個(gè)對(duì)象的“同一方法或其他同步方法”,只能阻塞等待,直到這個(gè)鎖再次變成可用的為止。
- 鎖還可以重入(reentrant),這意味著持有鎖的線程可以調(diào)用同一對(duì)象上的其他同步方法;當(dāng)最外層同步方法返回時(shí),會(huì)釋放該對(duì)象的內(nèi)在鎖。
- 靜態(tài)方法也可以同步,在這種情況下,會(huì)使用與該方法的類關(guān)聯(lián)的
Class
對(duì)象的鎖(每個(gè)類都有一個(gè)對(duì)應(yīng)的Class
對(duì)象,如Singleton.class
,包含該類的元數(shù)據(jù))。
public class Singleton {private static Singleton uniqueInstance;private Singleton() {}// synchronized 保證沒有兩個(gè)線程可以同時(shí)執(zhí)行 getInstance()// 一旦某個(gè)線程開始執(zhí)行 getInstance(),就會(huì)獲得鎖;其它線程再調(diào)用 getInstance(),會(huì)阻塞等待,直到持有鎖的線程釋放鎖public static synchronized Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}public String getDescription() { return "I'm a thread safe Singleton!"; }
}
同步方法實(shí)現(xiàn)簡(jiǎn)單,但是也有明顯的缺點(diǎn):
- 運(yùn)行時(shí)開銷大:同步一個(gè)方法可能會(huì)使性能下降 100 倍(synchronizing a method can decrease performance by a factor of 100);
- 存在不必要的資源浪費(fèi):實(shí)際上,只有第一次執(zhí)行
getInstance()
創(chuàng)建實(shí)例時(shí),才需要同步;然而,現(xiàn)在每次執(zhí)行getInstance()
都需要同步。
不過,如果 getInstance()
的性能對(duì)應(yīng)用來說并不重要,那么使用同步方法也沒有問題。
3.2 多線程方案2:急切實(shí)例化
在調(diào)用 getInstance()
時(shí)創(chuàng)建實(shí)例,被稱為延遲初始化(Lazy Initialization);
與之相對(duì)的,可以在類加載時(shí)直接創(chuàng)建實(shí)例,即急切初始化(Eager Initialization);因?yàn)轭惣虞d具有線程安全性,所以實(shí)例的創(chuàng)建也是線程安全的。
public class Singleton {// 在類加載時(shí)直接創(chuàng)建實(shí)例,初始化靜態(tài)變量private static Singleton uniqueInstance = new Singleton();private Singleton() {}// 直接返回已經(jīng)創(chuàng)建的實(shí)例public static Singleton getInstance() {return uniqueInstance;}public String getDescription() { return "I'm a thread safe Singleton!"; }
}
急切實(shí)例化的特點(diǎn)和相關(guān)影響如下:
- 特點(diǎn):一定會(huì)創(chuàng)建實(shí)例;
影響:如果應(yīng)用有可能不使用實(shí)例,那么會(huì)造成不必要的資源浪費(fèi)。 - 特點(diǎn):在應(yīng)用啟動(dòng)時(shí)就會(huì)創(chuàng)建實(shí)例;
影響:如果實(shí)例的創(chuàng)建或運(yùn)行比較消耗資源,那么會(huì)給應(yīng)用帶來一定的負(fù)擔(dān)。
如果應(yīng)用需要盡早的使用實(shí)例,或者即便使用的時(shí)間比較晚,但實(shí)例的創(chuàng)建和運(yùn)行負(fù)擔(dān)并不重,那么也可以選擇急切實(shí)例化。
3.3 多線程方案3:雙重檢查鎖定
雙重檢查鎖定(Double-Checked Locking):先檢查實(shí)例是否已經(jīng)創(chuàng)建,如果尚未創(chuàng)建,才進(jìn)行同步。
以此減少 同步方法 中 getInstance()
對(duì)同步的使用。(需要 Java 5 及以后版本)
Java 概念:塊同步(Block Synchronization)
Java 允許使用synchronized
關(guān)鍵字來鎖定任何對(duì)象,從而實(shí)現(xiàn)代碼塊的同步。
synchronized(object) { // 在 object 被鎖定的情況下執(zhí)行某些操作 }
在塊中的代碼執(zhí)行之后,鎖會(huì)被釋放。
Java 概念:可見性(Visibility)
在一個(gè)單線程程序中,讀取變量的值總是會(huì)得到最后寫入該變量的值。但是,在 Java 的多線程應(yīng)用程序中,一個(gè)線程可能看不到另一個(gè)線程所做的更改,除非在數(shù)據(jù)上執(zhí)行的操作是同步的。然而,同步是有代價(jià)的。如果想要的是可見性,而不需要互斥,那么可以使用volatile
關(guān)鍵字,該關(guān)鍵字可以確保當(dāng)一個(gè)線程修改了變量的值之后,新值對(duì)于其他線程立即可見。
Java 概念:指令重排序(Instruction Reordering)
編譯器或處理器為了優(yōu)化程序性能,可能會(huì)改變指令的執(zhí)行順序。
例如對(duì)于uniqueInstance = new Singleton();
語句,其包含的步驟示意如下:
memory = allocate(sizeof(Singleton.class));
在堆內(nèi)存中為對(duì)象分配空間construct(memory, Singleton.class);
在分配的內(nèi)存空間上調(diào)用構(gòu)造函數(shù)來初始化對(duì)象uniqueInstance = memory;
將對(duì)象引用指向分配的內(nèi)存空間在進(jìn)行指令重排序后,步驟3可能會(huì)在步驟2之前執(zhí)行。如果另一個(gè)線程在“步驟3之后、步驟2之前”訪問
uniqueInstance
,就會(huì)獲取到一個(gè)未完全初始化的實(shí)例。通過將uniqueInstance
聲明為volatile
,可以禁止這種重排序。這樣,當(dāng)uniqueInstance
不為null
時(shí),它所引用的實(shí)例就是完全初始化的。
public class Singleton {// 將 uniqueInstance 聲明為 volatile,確保其可見性,并禁止指令重排序private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getInstance() {if (uniqueInstance == null) { // 【第一次檢查】實(shí)例是否創(chuàng)建,只有尚未創(chuàng)建,才進(jìn)入同步塊;synchronized (Singleton.class) { // 嘗試加鎖,如果其它線程已經(jīng)加鎖,則阻塞等待;if (uniqueInstance == null) { // 成功加鎖后,【再次檢查】實(shí)例是否尚未創(chuàng)建,uniqueInstance = new Singleton(); // 因?yàn)樵谧枞却倪^程中,其它線程可能已經(jīng)創(chuàng)建實(shí)例。}}}return uniqueInstance;}public String getDescription() { return "I'm a thread safe Singleton!"; }
}
如果使用同步方法存在性能問題,那么使用雙重檢查鎖定,則可以兼顧性能和線程安全。
3.4 其它注意事項(xiàng)
除了創(chuàng)建實(shí)例時(shí)的線程安全問題,在 java 中,使用單例還有一些其它注意事項(xiàng):
- 類加載器問題
- 問題描述:如果有兩個(gè)或多個(gè)類加載器,就可以多次加載同一個(gè)類(每個(gè)類加載器一次)。如果這個(gè)類剛好是一個(gè)單例,就會(huì)有多于一個(gè)的實(shí)例。
- 解決辦法:確保單例通過同一個(gè)類加載器加載,通常使用系統(tǒng)類加載器(即啟動(dòng)類加載器)加載單例。
- 反射問題
- 問題描述:通過反射可以調(diào)用類的私有構(gòu)造方法,因此可能會(huì)創(chuàng)建類的多個(gè)實(shí)例。
- 解決辦法:在構(gòu)造方法中添加防御性代碼,防止通過反射創(chuàng)建多個(gè)實(shí)例。例如,可以在構(gòu)造方法中檢查是否已經(jīng)存在實(shí)例,如果存在則拋出異常。
- 序列化和反序列化問題
- 問題描述:當(dāng)單例實(shí)現(xiàn)了
Serializable
接口時(shí),序列化會(huì)將對(duì)象的狀態(tài)保存下來,之后反序列化可以重建對(duì)象,這樣可能會(huì)創(chuàng)建類的多個(gè)實(shí)例。 - 解決辦法:在單例中添加一個(gè)
readResolve
方法,該方法在反序列化時(shí)會(huì)被調(diào)用,可以返回單例實(shí)例,而不是創(chuàng)建一個(gè)新的實(shí)例。
- 問題描述:當(dāng)單例實(shí)現(xiàn)了
3.5 使用枚舉
Java 概念:枚舉(Enum)
在 Java 中,枚舉是一種特殊的類,是java.lang.Enum
的子類,它的特性包括:
- 枚舉類默認(rèn)具有私有的構(gòu)造方法,而且不允許顯式定義非私有構(gòu)造方法,因此無法從外部實(shí)例化;并且也不能通過反射來訪問構(gòu)造方法;
- 枚舉實(shí)例在類被加載到 JVM 時(shí)靜態(tài)初始化,保證了實(shí)例的唯一性和線程安全性;
- 枚舉類在序列化和反序列化的過程中,會(huì)由 JVM 保證枚舉實(shí)例的唯一性;
- 每個(gè)枚舉常量自動(dòng)被視為
public static final
,并且是枚舉類型的一個(gè)實(shí)例;- 枚舉類也可以定義自己的方法和變量。
因?yàn)?Java 會(huì)保證枚舉類中每個(gè)枚舉常量的唯一性,所以通過定義一個(gè)包含單個(gè)枚舉常量的枚舉類,就可以自然地實(shí)現(xiàn)單例模式。
public enum Singleton {UNIQUE_INSTANCE;// 可以添加有用的變量和方法public String getDescription() {return "I'm a thread safe Singleton!";}
}
枚舉的使用:
Singleton singleton = Singleton.UNIQUE_INSTANCE;
System.out.println(singleton.getDescription());
使用枚舉實(shí)現(xiàn)單例,代碼簡(jiǎn)潔明了,而且可以避免前文提到的所有單例問題,包括創(chuàng)建實(shí)例時(shí)的線程安全、類加載問題、反射問題、以及序列化和反序列化問題。因此,枚舉是實(shí)現(xiàn)單例模式的一種推薦方式。
4 示例代碼
4.1 Java 示例
雙重檢查鎖定方式:
// ChocolateBoiler.java
public class ChocolateBoiler {private boolean empty;private boolean boiled;private volatile static ChocolateBoiler uniqueInstance;private ChocolateBoiler() {empty = true;boiled = false;System.out.println("[" + Thread.currentThread().getName() + "] 創(chuàng)建巧克力鍋爐實(shí)例,初始狀態(tài):空、未沸");}public static ChocolateBoiler getInstance() {if (uniqueInstance == null) {synchronized (ChocolateBoiler.class) {if (uniqueInstance == null) {uniqueInstance = new ChocolateBoiler();}}}System.out.println("[" + Thread.currentThread().getName() + "] 返回巧克力鍋爐實(shí)例,當(dāng)前狀態(tài):" + (uniqueInstance.isEmpty() ? "空" : "滿") + "、" + (uniqueInstance.isBoiled() ? "沸騰" : "未沸"));return uniqueInstance;}public synchronized void fill() {System.out.println("[" + Thread.currentThread().getName() + "] 嘗試加滿,當(dāng)前狀態(tài):" + (isEmpty() ? "空" : "滿") + "、" + (isBoiled() ? "沸騰" : "未沸"));if (isEmpty()) {System.out.println(" => 向鍋爐中加滿牛奶和巧克力");empty = false;boiled = false;}}public synchronized void drain() {System.out.println("[" + Thread.currentThread().getName() + "] 嘗試排出,當(dāng)前狀態(tài):" + (isEmpty() ? "空" : "滿") + "、" + (isBoiled() ? "沸騰" : "未沸"));if (!isEmpty() && isBoiled()) {System.out.println(" => 從鍋爐中排出牛奶和巧克力");empty = true;boiled = false;}}public synchronized void boil() {System.out.println("[" + Thread.currentThread().getName() + "] 嘗試煮沸,當(dāng)前狀態(tài):" + (isEmpty() ? "空" : "滿") + "、" + (isBoiled() ? "沸騰" : "未沸"));if (!isEmpty() && !isBoiled()) {System.out.println(" => 將鍋爐中的牛奶和巧克力煮沸");boiled = true;}}public synchronized boolean isEmpty() { return empty; }public synchronized boolean isBoiled() { return boiled; }
}
枚舉方式:
// ChocolateBoilerEnum.java
public enum ChocolateBoilerEnum {UNIQUE_INSTANCE;private boolean empty;private boolean boiled;private ChocolateBoilerEnum() {empty = true;boiled = false;System.out.println("[" + Thread.currentThread().getName() + "] 創(chuàng)建巧克力鍋爐實(shí)例,初始狀態(tài):空、未沸");}public synchronized void fill() { /* 代碼相同 */ }public synchronized void drain() { /* 代碼相同 */ }public synchronized void boil() { /* 代碼相同 */ }public synchronized boolean isEmpty() { /* 代碼相同 */ }public synchronized boolean isBoiled() { /* 代碼相同 */ }
}
測(cè)試代碼:
// BoilerController.java
public class BoilerController {public static void main(String args[]) {Runnable boilerTask = new Runnable() {@Overridepublic void run() {ChocolateBoiler boiler = ChocolateBoiler.getInstance();// ChocolateBoilerEnum boiler = ChocolateBoilerEnum.UNIQUE_INSTANCE;boiler.fill(); // 加滿boiler.boil(); // 煮沸boiler.drain(); // 排出}};Thread thread1 = new Thread(boilerTask);Thread thread2 = new Thread(boilerTask);thread1.start();thread2.start();}
}
4.2 C++11 示例
4.2.1 Meyers Singleton
Meyers Singleton 是一種在 C++ 中實(shí)現(xiàn)單例模式的簡(jiǎn)潔方法,由 Scott Meyers(Effective C++ 的作者)提出。
struct Singleton {static Singleton& getInstance() {static Singleton uniqueInstance; return uniqueInstance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;
};
這種方法基于 C++11 的保證,即局部靜態(tài)對(duì)象會(huì)在“函數(shù)第一次執(zhí)行到該對(duì)象的定義處”時(shí)被初始化(同時(shí)具有線程安全性)。作為額外的好處,如果從未調(diào)用“模擬非局部靜態(tài)對(duì)象”的函數(shù)(即
getInstance()
函數(shù),使用在函數(shù)內(nèi)部定義的“局部靜態(tài)對(duì)象”,模擬在全局/命名空間范圍內(nèi)定義的“非局部靜態(tài)對(duì)象”),就永遠(yuǎn)不會(huì)產(chǎn)生構(gòu)造和析構(gòu)該對(duì)象的開銷。
Scott Myers says: “This approach is founded on C++'s guarantee that local static objects are initialized when the object’s definition is first encountered during a call to that function.” … “As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object.”
4.2.2 ChocolateBoiler
局部靜態(tài)變量方式(Meyers Singleton):
struct ChocolateBoiler {static ChocolateBoiler& getInstance() {// 對(duì)于局部靜態(tài)變量,由 C++11 保證只在第一次執(zhí)行到變量定義處時(shí),進(jìn)行初始化,并且是線程安全的static ChocolateBoiler uniqueInstance;std::cout << "[" << std::this_thread::get_id() << "] 返回巧克力鍋爐實(shí)例,當(dāng)前狀態(tài):" << (uniqueInstance.isEmpty() ? "空" : "滿") << "、" << (uniqueInstance.isBoiled() ? "沸騰" : "未沸") << '\n';return uniqueInstance;}void fill() {std::lock_guard<std::recursive_mutex> guard(mtx);std::cout << "[" << std::this_thread::get_id() << "] 嘗試加滿,當(dāng)前狀態(tài):" << (isEmpty() ? "空" : "滿") << "、" << (isBoiled() ? "沸騰" : "未沸") << '\n';if (isEmpty()) {std::cout << " => 向鍋爐中加滿牛奶和巧克力\n";empty = false;boiled = false;}}void drain() {std::lock_guard<std::recursive_mutex> guard(mtx);std::cout << "[" << std::this_thread::get_id() << "] 嘗試排出,當(dāng)前狀態(tài):" << (isEmpty() ? "空" : "滿") << "、" << (isBoiled() ? "沸騰" : "未沸") << '\n';if (!isEmpty() && isBoiled()) {std::cout << " => 從鍋爐中排出牛奶和巧克力\n";empty = true;boiled = false;}}void boil() {std::lock_guard<std::recursive_mutex> guard(mtx);std::cout << "[" << std::this_thread::get_id() << "] 嘗試煮沸,當(dāng)前狀態(tài):" << (isEmpty() ? "空" : "滿") << "、" << (isBoiled() ? "沸騰" : "未沸") << '\n';if (!isEmpty() && !isBoiled()) {std::cout << " => 將鍋爐中的牛奶和巧克力煮沸\(zhòng)n";boiled = true;}}bool isEmpty() const {std::lock_guard<std::recursive_mutex> guard(mtx);return empty;}bool isBoiled() const {std::lock_guard<std::recursive_mutex> guard(mtx);return boiled;}ChocolateBoiler(const ChocolateBoiler&) = delete;ChocolateBoiler& operator=(const ChocolateBoiler&) = delete;private:ChocolateBoiler() : empty(true), boiled(false) {std::cout << "[" << std::this_thread::get_id() << "] 創(chuàng)建巧克力鍋爐實(shí)例,初始狀態(tài):空、未沸\(zhòng)n";}bool empty;bool boiled;static std::recursive_mutex mtx;
};std::recursive_mutex ChocolateBoiler::mtx;
測(cè)試代碼:
#include <iostream>
#include <mutex>
#include <thread>// 在這里添加相關(guān)接口和類的定義int main() {std::function<void()> boilerTask = []() {ChocolateBoiler& boiler = ChocolateBoiler::getInstance();boiler.fill();boiler.boil();boiler.drain();};std::thread t1(boilerTask);std::thread t2(boilerTask);t1.join();t2.join();
}
4.2.3 一次性互斥
與 Java 的雙重檢查鎖定相比,C++11 提供了更為簡(jiǎn)潔的“一次性互斥”機(jī)制。
通過 std::once_flag
類和 std::call_once()
函數(shù)模板來實(shí)現(xiàn)一次性互斥,確保即使有多個(gè)線程、多次、同時(shí)調(diào)用某個(gè)函數(shù)(可調(diào)用對(duì)象),其只會(huì)被執(zhí)行一次。復(fù)習(xí)回顧 線程池2-線程互斥 => 3.1.3 一次性互斥。
struct Singleton {static Singleton& getInstance() {std::call_once(initFlag, []() { uniqueInstance.reset(new Singleton()); });return *uniqueInstance;}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:Singleton() = default;static std::unique_ptr<Singleton> uniqueInstance;static std::once_flag initFlag;
};std::unique_ptr<Singleton> Singleton::uniqueInstance;
std::once_flag Singleton::initFlag;
5 設(shè)計(jì)工具箱
5.1 OO 基礎(chǔ)
OO 基礎(chǔ)回顧
- 抽象(Abstraction)
- 封裝(Encapsulation)
- 繼承(Inheritance)
- 多態(tài)(Polymorphism)
5.2 OO 原則
5.2.1 新原則
本章沒有介紹新的 OO 原則。
5.2.2 原則回顧
- 封裝變化。
Encapsulate what varies. - 針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。
Program to interfaces, not implementations. - 優(yōu)先使用組合,而不是繼承。
Favor composition over inheritance. - 盡量做到交互對(duì)象之間的松耦合設(shè)計(jì)。
Strive for loosely coupled designs between objects that interact. - 類應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。
Classes should be open for extension, but closed for modification. - 依賴抽象,不依賴具體類。
Depend on abstractions. Do not depend on concrete classes.
5.3 OO 模式
5.3.1 新模式
單例模式(Singleton Pattern)
- 確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。
The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it.
5.3.2 模式回顧
1 創(chuàng)建型模式(Creational Patterns)
創(chuàng)建型模式與對(duì)象的創(chuàng)建有關(guān)。
Creational patterns concern the process of object creation.
- 工廠方法(Factory Method)
- 定義了一個(gè)創(chuàng)建對(duì)象的接口,但由子類決定要實(shí)例化哪個(gè)類。
The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. - 工廠方法讓類把實(shí)例化推遲到子類。
Factory Method lets a class defer instantiation to subclasses.
- 定義了一個(gè)創(chuàng)建對(duì)象的接口,但由子類決定要實(shí)例化哪個(gè)類。
- 抽象工廠(Abstract Factory)
- 提供一個(gè)接口,創(chuàng)建相關(guān)或依賴對(duì)象的家族,而不需要指定具體類。
The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- 提供一個(gè)接口,創(chuàng)建相關(guān)或依賴對(duì)象的家族,而不需要指定具體類。
2 結(jié)構(gòu)型模式(Structural Patterns)
結(jié)構(gòu)型模式處理類或?qū)ο蟮慕M合。
Structural patterns deal with the composition of classes or objects.
- 裝飾者模式(Decorator Pattern)
- 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。
The Decorator Pattern attaches additional responsibilities to an object dynamically. - 就增加功能來說,裝飾者模式相比生成子類更為靈活。
Decorators provide a flexible alternative to subclassing for extending functionality.
- 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。
3 行為型模式(Behavioral Patterns)
行為型模式描述類或?qū)ο笾g的交互方式以及職責(zé)分配方式。
Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.
- 策略模式(Strategy Pattern)
- 定義一個(gè)算法家族,把其中的算法分別封裝起來,使得它們之間可以互相替換。
Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. - 讓算法的變化獨(dú)立于使用算法的客戶。
Strategy lets the algorithm vary independently from clients that use it.
- 定義一個(gè)算法家族,把其中的算法分別封裝起來,使得它們之間可以互相替換。
- 觀察者模式(Observer Pattern)
- 定義對(duì)象之間的一對(duì)多依賴,
The Observer Pattern defines a one-to-many dependency between objects - 這樣一來,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴者都會(huì)被通知并自動(dòng)更新。
so that when one object changes state, all of its dependents are notified and updated automatically.
- 定義對(duì)象之間的一對(duì)多依賴,
參考
- [美]弗里曼、羅布森著,UMLChina譯.Head First設(shè)計(jì)模式.中國(guó)電力出版社.2022.2
- [美]伽瑪?shù)戎?李英軍等譯.設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ).機(jī)械工業(yè)出版社.2019.3
- wickedlysmart: Head First設(shè)計(jì)模式 Java 源碼
- [加]布迪·克尼亞萬著,沈澤剛譯.Java經(jīng)典入門指南.人民郵電出版社.2020.6
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.