單例 vs 單一實例
原文:Singleton vs. single instance
歡迎大家來到 Monologue,今天我們討論一個同程序設計相關的話題坛增,其不僅適用于 iOS 建椰,更適用于所有的程序開發(fā)雕欺。
雖然我并非是是程序設計方面的專家,但在個人看來棉姐,許多 app 項目對單例 / 單一實例的使用存在混淆不清的地方屠列,更可怕的是,開發(fā)者們似乎還沒有意識到伞矩。
因此笛洛,我想在這里同大家分享如何避免這樣的設計缺陷。如前所述乃坤,我并非程序設計方面的專家苛让,所以無法保證在這篇文章中所陳述的觀點100%正確,歡迎大家批評指正湿诊。我更傾向于讓將篇文章起到拋磚引玉的作用狱杰,引發(fā)討論,從而解決當前的問題厅须。所以仿畸,歡迎評論。
什么是“單例 vs 單一實例”
閑話少說朗和,直入正題:什么是“單例 vs 單一實例”
追根溯源
我已記不得誰是第一個如此描述這個問題的人错沽,但是我依然記得是從哪里讀到這個術語的。那是一本名叫《玩轉老舊代碼》的書(Working effectively with legacy code by Michael Feathers)眶拉。如果你不知道這它千埃,建議去讀讀。其中包含了許多有用的技巧忆植,即使你認為自己從不需要和老舊代碼打交道镰禾,依然可以從其中受益良多皿曲。
定義
“單例 vs 單一實例”表示一個很簡單的概念:當你使用單例的時候,先問問自己是否可以改用單一實例吴侦。
按照單例模式設計的類(以下簡稱單例類)在整個 app 中有且只有一個實例對象屋休,通常,在程序的任意地方都可以訪問它备韧。
相反劫樟,“單一實例”意味著類本身并非按照單例模式進行設計,但在使用時织堂,我們每次只使用一個實例對象叠艳。
乍看上去,好像沒什么大不了的易阳,我們甚至會覺得單例更棒更好用附较。其實不然,且聽我慢慢道來:
假象
封裝性
不管是單例還是單一實例潦俺,我們都只使用一個實例對象拒课。但是前者通過設計模式貫徹這一原則,后者僅僅依靠使用者主觀遵守這個原則事示。很明顯早像,在這種情況下,最好能夠對使用者進行強制約束肖爵,所以卢鹦,就封裝性來看,單例勝出劝堪。
易用性
開發(fā)者都是懶人(也應該是)冀自,喜歡簡單的接口。從這點上來講秒啦,單例無人能及熬粗。只需要引入頭文件(Swift 不用),調用返回單例對象的類方法帝蒿,就 OK 了。夠簡單吧巷怜?如果使用單一實例
葛超,我們首先必須搞清楚誰擁有這個實例對象 & 如何能夠獲取到它。
不過延塑,好用并不總是好事绣张。說到這里,希望大家能夠有所警覺关带,我們繼續(xù)往下看侥涵。
進階
測試驅動開發(fā)
同許多《玩轉老舊代碼》探討的主題一樣沼撕,這個主題也提到了測試驅動開發(fā)
(以下簡稱 TDD)。即使你反對測試驅動開發(fā)芜飘,也不著急關閉頁面务豺。
TDD 的關鍵在于各項測試獨立進行,程序環(huán)境在每次測試之間都會重置嗦明。此時笼沥,單例
會造成麻煩:整個 app 的生命周期中有且僅有一個實例對象存在,我們無法保證這個對象是否還殘留有前一個測試的狀態(tài)信息(另外還需注意娶牌,許多 IDE 可以同時運行多個單元測試奔浅,它們之間的順序無法保證)。這個問題是可以解決的诗良,但總的來說汹桦,針對 TDD,“單例 vs 單一實例”之間較量為 0:1鉴裹。
限制訪問
同“易用性”相反舞骆,有時,我們必須限制對于某些對象的訪問壹罚。
嗯葛作,全局訪問
有著天生的缺陷。如果在整個程序的任何地方都可以訪問一個對象猖凛,那么一旦出現(xiàn)問題赂蠢,就很難知道是誰進行了誤操作。試想一下找出一個被30個不同對象訪問的單例對象除了問題辨泳,這種 debug 極為麻煩虱岂。
另一個問題就是越好用的東西就越會被頻繁使用
。這就會造成上一段文字討論的局面菠红,所有對象都肆意的調用單例第岖。
并不是說絕對不可以使用單例,從功能上來說這種方式?jīng)]問題试溯。但是它很容易被濫用(事實也是如此)蔑滓,例如下面的例子:
例子
就我個人而言,單例模式及其好用遇绞,但必須意識到我們正在濫用它键袱。如果你選擇使用它,請三思:是不是只能有一個實例對象摹闽;如果有兩個同時并存拜姿,就會破壞程序的結構屡谐?換句話來說,是不是一個對象就夠了荐吵?
最常見的濫用單例的典型:當我們需要一個全局變量時。許多現(xiàn)代編程語言都強調盡量避免使用全局變量
,但在有時我們不得不用。我們創(chuàng)建一個單例對象,因為在哪里都可以引用它(同全局變量)谁不,試著回答上面的問題:
是不是只能有一個實例對象,或者說是不是一個對象就足夠了务蝠?
當然不是拍谐!
當然,上面只是舉了一個很基礎的例子馏段。想要觸及真正的問題轩拨, 就必須進一步深入挖掘。
以 MVC 架構中的控制器為例院喜,它是處理業(yè)務邏輯的地方亡蓉。我見過許多項目都在它們的業(yè)務邏輯中頻繁使用單例:CommunicatinManager,DataManager喷舀,NotificationMananger砍濒,LoginMananger等等。它們都不約而同的使用了單例硫麻,但問題是:有必要嗎爸邢?
拿 LoginMananger 來說,這個對象負責管理用戶登陸周期拿愧,其包含 token / cookie / credential 等信息杠河。
大多數(shù) app 一次只允許一個用戶登陸。所以浇辜,我們只一次只需要一個 LoginManager 實例對象券敌。乍一看來,單例完美無缺柳洋。但回到上面的問題:
是不是只能有一個實例對象待诅,或者說是不是一個對象就足夠了?
是的熊镣,沒錯卑雁。如果同時出現(xiàn)兩個 LoginManager 那就有問題了。等等绪囱,不覺得有點奇怪嗎测蹲?考慮下面的情況:
- 登入
- 登出
- 再次登陸,但使用不同的證書
氨瞎俊弛房!雖然整個程序只需要一個 LoginMananger 實例對象道盏,但是這個其在程序運行的期間發(fā)生了變化而柑。所以文捶,上述問題可以修正為:
整個 app 的生命周期中,是不是只能有一個
一成不變
的實例對象媒咳?
對于單例模式來說粹排, LoginManager 對象在不同用戶登陸周期之間持續(xù)存在。因此涩澡,用戶登出時顽耳,這個對象必須清除其所保存的用戶信息。貌似簡單妙同,用戶的登陸狀態(tài)是通過若干項信息表示的-token射富,用戶名等≈嘀悖可以其他用戶相關數(shù)據(jù)胰耗,諸如緩存的好友列表,頭像芒涡,密碼呢柴灯?這些信息是很難維護的。你不能指望你的同事(甚至你自己)記得在登出時清除數(shù)據(jù)费尽。
某次赠群,如果你忘記清理用戶 token,會發(fā)生什么旱幼?用戶可能會以錯誤的身份能登陸查描!
如果 LoginMananger 對象不是一個單例,我們只需在用戶登出時刪除這個對象即可速警。完全不用擔心自己忘記清理數(shù)據(jù)叹誉。
同現(xiàn)實生活類似,在軟件中沒有什么是永恒的闷旧。所以別舍不得釋放你的對象长豁,否則難過的是你??。
好吧忙灼,今天的關于“單例 vs 單一實例”的討論就到這里匠襟,感謝閱讀??!