23種設(shè)計(jì)模式漫畫版系列—單例模式

01意圖

單例模式是一種創(chuàng)建型設(shè)計(jì)模式, 讓你能夠保證一個(gè)類只有一個(gè)實(shí)例叹话, 并提供一個(gè)訪問該實(shí)例的全局節(jié)點(diǎn)。

02問題

單例模式同時(shí)解決了兩個(gè)問題墩瞳, 所以違反了_單一職責(zé)原則_:

? ? 1.? 保證一個(gè)類只有一個(gè)實(shí)例驼壶。 為什么會(huì)有人想要控制一個(gè)類所擁有的實(shí)例數(shù)量? 最常見的原因是控制某些共享資源 (例如數(shù)據(jù)庫(kù)或文件) 的訪問權(quán)限喉酌。

它的運(yùn)作方式是這樣的: 如果你創(chuàng)建了一個(gè)對(duì)象热凹, 同時(shí)過一會(huì)兒后你決定再創(chuàng)建一個(gè)新對(duì)象, 此時(shí)你會(huì)獲得之前已創(chuàng)建的對(duì)象泪电, 而不是一個(gè)新對(duì)象般妙。

注意, 普通構(gòu)造函數(shù)無法實(shí)現(xiàn)上述行為相速, 因?yàn)闃?gòu)造函數(shù)的設(shè)計(jì)決定了它必須總是返回一個(gè)新對(duì)象碟渺。

客戶端甚至可能沒有意識(shí)到它們一直都在使用同一個(gè)對(duì)象。

? 2. 為該實(shí)例提供一個(gè)全局訪問節(jié)點(diǎn)突诬。 還記得你 (好吧苫拍, 其實(shí)是我自己) 用過的那些存儲(chǔ)重要對(duì)象的全局變量嗎? 它們?cè)谑褂蒙鲜址奖悖?但同時(shí)也非常不安全旺隙, 因?yàn)槿魏未a都有可能覆蓋掉那些變量的內(nèi)容绒极, 從而引發(fā)程序崩潰。

和全局變量一樣蔬捷, 單例模式也允許在程序的任何地方訪問特定對(duì)象垄提。 但是它可以保護(hù)該實(shí)例不被其他代碼覆蓋。

還有一點(diǎn): 你不會(huì)希望解決同一個(gè)問題的代碼分散在程序各處的。 因此更好的方式是將其放在同一個(gè)類中铡俐, 特別是當(dāng)其他代碼已經(jīng)依賴這個(gè)類時(shí)更應(yīng)該如此摘昌。

如今, 單例模式已經(jīng)變得非常流行高蜂, 以至于人們會(huì)將只解決上文描述中任意一個(gè)問題的東西稱為單例聪黎。

03解決方案

所有單例的實(shí)現(xiàn)都包含以下兩個(gè)相同的步驟:

  • 將默認(rèn)構(gòu)造函數(shù)設(shè)為私有, 防止其他對(duì)象使用單例類的 new運(yùn)算符备恤。

  • 新建一個(gè)靜態(tài)構(gòu)建方法作為構(gòu)造函數(shù)稿饰。 該函數(shù)會(huì) “偷偷” 調(diào)用私有構(gòu)造函數(shù)來創(chuàng)建對(duì)象, 并將其保存在一個(gè)靜態(tài)成員變量中露泊。 此后所有對(duì)于該函數(shù)的調(diào)用都將返回這一緩存對(duì)象喉镰。

如果你的代碼能夠訪問單例類, 那它就能調(diào)用單例類的靜態(tài)方法惭笑。 無論何時(shí)調(diào)用該方法侣姆, 它總是會(huì)返回相同的對(duì)象。

04真實(shí)世界類比

政府是單例模式的一個(gè)很好的示例沉噩。 一個(gè)國(guó)家只有一個(gè)官方政府捺宗。 不管組成政府的每個(gè)人的身份是什么,? “某政府” 這一稱謂總是鑒別那些掌權(quán)者的全局訪問節(jié)點(diǎn)川蒙。

05單例模式結(jié)構(gòu)

06偽代碼

在本例中蚜厉, 數(shù)據(jù)庫(kù)連接類即是一個(gè)單例。 該類不提供公有構(gòu)造函數(shù)畜眨, 因此獲取該對(duì)象的唯一方式是調(diào)用 獲取實(shí)例方法昼牛。 該方法將緩存首次生成的對(duì)象, 并為所有后續(xù)調(diào)用返回該對(duì)象康聂。

// 數(shù)據(jù)庫(kù)類會(huì)對(duì)`getInstance(獲取實(shí)例)`方法進(jìn)行定義以讓客戶端在程序各處
// 都能訪問相同的數(shù)據(jù)庫(kù)連接實(shí)例贰健。
class Database is
? ? // 保存單例實(shí)例的成員變量必須被聲明為靜態(tài)類型。
? ? private static field instance: Database

? ? // 單例的構(gòu)造函數(shù)必須永遠(yuǎn)是私有類型恬汁,以防止使用`new`運(yùn)算符直接調(diào)用構(gòu)
? ? // 造方法伶椿。
? ? private constructor Database() is
? ? ? ? // 部分初始化代碼(例如到數(shù)據(jù)庫(kù)服務(wù)器的實(shí)際連接)。
? ? ? ? // ...

? ? // 用于控制對(duì)單例實(shí)例的訪問權(quán)限的靜態(tài)方法蕊连。
? ? public static method getInstance() is
? ? ? ? if (Database.instance == null) then
? ? ? ? ? ? acquireThreadLock() and then
? ? ? ? ? ? ? ? // 確保在該線程等待解鎖時(shí)悬垃,其他線程沒有初始化該實(shí)例。
? ? ? ? ? ? ? ? if (Database.instance == null) then
? ? ? ? ? ? ? ? ? ? Database.instance = new Database()
? ? ? ? return Database.instance

? ? // 最后甘苍,任何單例都必須定義一些可在其實(shí)例上執(zhí)行的業(yè)務(wù)邏輯尝蠕。
? ? public method query(sql) is
? ? ? ? // 比如應(yīng)用的所有數(shù)據(jù)庫(kù)查詢請(qǐng)求都需要通過該方法進(jìn)行。因此载庭,你可以
? ? ? ? // 在這里添加限流或緩沖邏輯看彼。
? ? ? ? // ...

class Application is
? ? method main() is
? ? ? ? Database foo = Database.getInstance()
? ? ? ? foo.query("SELECT ...")
? ? ? ? // ...
? ? ? ? Database bar = Database.getInstance()
? ? ? ? bar.query("SELECT ...")
? ? ? ? // 變量 `bar` 和 `foo` 中將包含同一個(gè)對(duì)象廊佩。

07應(yīng)用場(chǎng)景

  • 如果程序中的某個(gè)類對(duì)于所有客戶端只有一個(gè)可用的實(shí)例, 可以使用單例模式靖榕。

  • 單例模式禁止通過除特殊構(gòu)建方法以外的任何方式來創(chuàng)建自身類的對(duì)象标锄。 該方法可以創(chuàng)建一個(gè)新對(duì)象, 但如果該對(duì)象已經(jīng)被創(chuàng)建茁计, 則返回已有的對(duì)象料皇。

  • 如果你需要更加嚴(yán)格地控制全局變量, 可以使用單例模式星压。

  • 單例模式與全局變量不同践剂, 它保證類只存在一個(gè)實(shí)例。 除了單例類自己以外娜膘, 無法通過任何方式替換緩存的實(shí)例逊脯。

  • 請(qǐng)注意, 你可以隨時(shí)調(diào)整限制并設(shè)定生成單例實(shí)例的數(shù)量竣贪, 只需修改 獲取實(shí)例方法军洼, 即 getInstance 中的代碼即可實(shí)現(xiàn)。

08單例模式優(yōu)缺點(diǎn)
  • 你可以保證一個(gè)類只有一個(gè)實(shí)例演怎。

  • 你獲得了一個(gè)指向該實(shí)例的全局訪問節(jié)點(diǎn)匕争。

  • 僅在首次請(qǐng)求單例對(duì)象時(shí)對(duì)其進(jìn)行初始化。

  • 違反了_單一職責(zé)原則颤枪。 該模式同時(shí)解決了兩個(gè)問題汗捡。

  • 單例模式可能掩蓋不良設(shè)計(jì)淑际, 比如程序各組件之間相互了解過多等畏纲。

  • 該模式在多線程環(huán)境下需要進(jìn)行特殊處理, 避免多個(gè)線程多次創(chuàng)建單例對(duì)象春缕。

  • 單例的客戶端代碼單元測(cè)試可能會(huì)比較困難盗胀, 因?yàn)樵S多測(cè)試框架以基于繼承的方式創(chuàng)建模擬對(duì)象。 由于單例類的構(gòu)造函數(shù)是私有的锄贼, 而且絕大部分語言無法重寫靜態(tài)方法票灰, 所以你需要想出仔細(xì)考慮模擬單例的方法。 要么干脆不編寫測(cè)試代碼宅荤, 或者不使用單例模式屑迂。

    09Python、Java 代碼示例

詳見次條文章

09

10冯键、推薦UML使用工具

億圖圖示

初學(xué)者秒會(huì)的專業(yè)級(jí)UML圖繪制軟件惹盼。無需掌握復(fù)雜操作,可以零基礎(chǔ)輕松繪制280+種繪圖類型

Visio

11惫确、UML? 類圖關(guān)系

UML類圖非常簡(jiǎn)單手报,可以用下面的圖表示一個(gè)類:

該圖表示一個(gè)叫做Person的類蚯舱,該類有name、age掩蛤、sex三個(gè)private屬性枉昏,每個(gè)屬性的類型緊跟在冒號(hào)的后面。該類有walk和speak兩個(gè)方法揍鸟,其中walk方法是public的兄裂,而speak方法是protected的,兩個(gè)方法的返回值類型緊跟在冒號(hào)的后面阳藻。

+:公有屬性懦窘,其它類可以訪問該屬性

-:私有屬性,不能被其它類訪問(默認(rèn)為私有)

\#:保護(hù)屬性稚配,只能被本類及其派生類訪問

~:包內(nèi)可見畅涂,可以被本包中的其它類訪問

如果要表示一個(gè)接口,則用下面的圖表示:

下面介紹類與類之間的關(guān)系道川。如果按照關(guān)系的緊密程度從弱到強(qiáng)劃分午衰,類與類之間的關(guān)系包括:

  • 依賴

  • 關(guān)聯(lián)

  • 聚合

  • 組合

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

  • 繼承

依賴關(guān)系

依賴關(guān)系是所有類間關(guān)系中最弱的一種,它用下面的圖表示:

圖中的箭頭方向表示依賴的方向冒萄,上圖表示類A依賴類B臊岸。

依賴,顧名思義表示一個(gè)實(shí)體的存在必須依賴另一個(gè)實(shí)體的存在尊流∷Ы洌可以這樣認(rèn)為,如果類A依賴類B崖技,那么類A只有在類B存在的情況下逻住,才能編譯通過。

下面代碼是依賴的一個(gè)例子:

public class UserController {
private UserService userService;

public User query(Strint userId) {
User user = userService.queryUser(userId);

return user;

}

}

在這段代碼迎献,UserController類同時(shí)依賴于UserService和User兩個(gè)類瞎访,可以用下面的類圖表示它們的依賴關(guān)系:

可見依賴關(guān)系大量的存在于我們的代碼中,但千萬不要在項(xiàng)目設(shè)計(jì)時(shí)將全部的依賴關(guān)系都畫出來吁恍,這不僅很累扒秸,而且也沒有必要。當(dāng)梳理依賴關(guān)系時(shí)冀瓦,先要搞清楚你關(guān)注什么伴奥,想表達(dá)什么,只畫出真正需要畫的就可以翼闽。

關(guān)聯(lián)關(guān)系

關(guān)聯(lián)關(guān)系表示兩個(gè)實(shí)體間存在一定的聯(lián)系拾徙,這種聯(lián)系比依賴關(guān)系更緊密,不僅僅只是“兩個(gè)實(shí)體觸碰到”這樣松散的關(guān)系肄程。例如Student和School這兩個(gè)類锣吼,一個(gè)學(xué)生一定會(huì)有一個(gè)對(duì)應(yīng)的學(xué)校选浑,那么Student和School間就存在關(guān)聯(lián)關(guān)系,且它們的關(guān)系是一對(duì)多的玄叠。

用下面的UML圖表示:

關(guān)聯(lián)關(guān)系也可以用于領(lǐng)域建模古徒,例如要設(shè)計(jì)一個(gè)骰子游戲,游戲者連續(xù)投擲兩次篩子读恃,如果兩次點(diǎn)數(shù)的總數(shù)是7隧膘,則游戲者贏,否則游戲者輸寺惫≌畛裕可以用下面UML圖對(duì)這個(gè)問題進(jìn)行領(lǐng)域建模,各實(shí)體間使用的就是關(guān)聯(lián)關(guān)系西雀。這也是關(guān)聯(lián)關(guān)系的一種特殊用法萨驶。

聚合&組合

聚合也是一種關(guān)聯(lián)關(guān)系,但是這種關(guān)聯(lián)關(guān)系存在整體與部分的語義艇肴。例如大雁和大雁群腔呜,一只大雁是整個(gè)大雁群的一部分。這就是一種聚合關(guān)系再悼,具有has-a的語義核畴。下面的UML圖用來描述聚合關(guān)系。

組合是一種強(qiáng)聚合關(guān)系冲九,它表示整體和部分之間具有相同的生命周期谤草,同生共死。例如鳥和翅膀莺奸,鳥如果死掉了丑孩,那么它的翅膀也會(huì)跟著死掉。組合關(guān)系具有contains-a的語義憾筏。下面的UML圖用于表達(dá)組合關(guān)系嚎杨。

記憶聚合和組合UML圖畫法的小技巧:菱形就相當(dāng)于一個(gè)容器,容器指向的實(shí)體就是整體氧腰,所以上面圖中的菱形分別指向大雁群和鳥。此外刨肃,由于組合關(guān)系的緊密程度比聚合關(guān)系更強(qiáng)古拴,所以組合關(guān)系用實(shí)心菱形,聚合關(guān)系用空心菱形真友。

繼承&實(shí)現(xiàn)

繼承和實(shí)現(xiàn)都是Java中的基礎(chǔ)黄痪,比較容易理解,它們是類與類之間關(guān)系最強(qiáng)的盔然。分別用下面的UML圖表示桅打。

繼承示例:

實(shí)現(xiàn)示例:

PS:實(shí)現(xiàn)關(guān)系應(yīng)該用空心箭頭

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末是嗜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挺尾,更是在濱河造成了極大的恐慌鹅搪,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遭铺,死亡現(xiàn)場(chǎng)離奇詭異丽柿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)魂挂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門甫题,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涂召,你說我怎么就攤上這事坠非。” “怎么了果正?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵麻顶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我舱卡,道長(zhǎng)辅肾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任轮锥,我火速辦了婚禮矫钓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舍杜。我一直安慰自己新娜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布既绩。 她就那樣靜靜地躺著概龄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饲握。 梳的紋絲不亂的頭發(fā)上私杜,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音救欧,去河邊找鬼衰粹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笆怠,可吹牛的內(nèi)容都是我干的铝耻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蹬刷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瓢捉!你這毒婦竟也來了频丘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤泡态,失蹤者是張志新(化名)和其女友劉穎搂漠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兽赁,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡状答,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刀崖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊科。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亮钦,靈堂內(nèi)的尸體忽然破棺而出馆截,到底是詐尸還是另有隱情,我是刑警寧澤蜂莉,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布蜡娶,位于F島的核電站,受9級(jí)特大地震影響映穗,放射性物質(zhì)發(fā)生泄漏窖张。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一蚁滋、第九天 我趴在偏房一處隱蔽的房頂上張望宿接。 院中可真熱鬧,春花似錦辕录、人聲如沸睦霎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽副女。三九已至,卻和暖如春蚣旱,著一層夾襖步出監(jiān)牢的瞬間碑幅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工姻锁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枕赵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓位隶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親开皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涧黄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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