@[toc]
1. 按
眾所周知,對(duì)象是解決繼承的問題的钳枕,如果沒有對(duì)象,那么將很難解決繼承的問題赏壹。這點(diǎn)兒和人類的現(xiàn)實(shí)世界有相似之處鱼炒,沒有對(duì)象的話何談子嗣的繼承問題?
沒有對(duì)象蝌借,將意味著很難實(shí)現(xiàn)繼承時(shí)的多態(tài)昔瞧、很難去塑造子類,沒有子類又將意味著父類在設(shè)計(jì)時(shí)要具備所有的功能骨望,這絕對(duì)是極不現(xiàn)實(shí)的硬爆。一種很現(xiàn)實(shí)的設(shè)計(jì)是:父類先解決一些問題,然后子類再解決一些問題擎鸠,接著子類的子類再解決一些問題缀磕,這樣子子孫孫無窮盡也,一定能夠把所有問題都解決好的劣光,這種設(shè)計(jì)模式也一定能夠應(yīng)對(duì)復(fù)雜多變的現(xiàn)實(shí)環(huán)境袜蚕,因此對(duì)象的存在意義重大。
但對(duì)象是越多越好嗎绢涡?答案絕對(duì)是否定的牲剃,很多情況下我們只需要一個(gè)對(duì)象就可以了,多余的對(duì)象會(huì)帶來很多的不必要的開銷和麻煩雄可。這是因?yàn)槊總€(gè)對(duì)象都要占據(jù)一定的內(nèi)存空間和CPU算力凿傅,大多數(shù)情況下我們只需要一個(gè)對(duì)象去執(zhí)行任務(wù),對(duì)于一顆CPU的核心而言数苫,操作一個(gè)對(duì)象是最快的聪舒,為什么?這主要是由于線程的切換會(huì)造成不必要的路程開銷虐急。
設(shè)想一下箱残,假如你有一個(gè)對(duì)象可以用來接吻,你的DNA序列約定你一生要完成4800次接吻才算完成了任務(wù)止吁。這樣在你臨死的時(shí)候才會(huì)死而無憾被辑,即程序完成所有的任務(wù)后正常退出,返回值為0敬惦,代表剩余待做的任務(wù)數(shù)為0盼理。平均你和一個(gè)對(duì)象每天的最多能接吻48次,這樣只和單個(gè)對(duì)象全力接吻的話100天就能完成任務(wù)俄删。
但如果你有多個(gè)對(duì)象的話榜揖,你的平均作戰(zhàn)能力并不會(huì)提升勾哩,這是因?yàn)槊刻旖游?8次是你的處理極限,相反举哟,你因?yàn)橐投鄠€(gè)對(duì)象進(jìn)行接吻,每次從一個(gè)對(duì)象移動(dòng)到另一個(gè)對(duì)象那里會(huì)產(chǎn)生不必要的路程開銷迅矛,會(huì)大大影響你的工作效率妨猩。
假設(shè)你有兩個(gè)對(duì)象,對(duì)象A在河南秽褒,對(duì)象B在河北壶硅,你從河南到河北需要一天的時(shí)間,你每天工作完了之后會(huì)切換到另一個(gè)對(duì)象那里销斟。假設(shè)先從與對(duì)象A接吻開始庐椒,工作一天共接吻48次,但第二天你需要移動(dòng)到對(duì)象B那里蚂踊,我們知道约谈,移動(dòng)的過程中是沒有對(duì)象可以接吻的,只有到達(dá)目的地找到人之后才能進(jìn)入工作狀態(tài)犁钟。
你切換任務(wù)的過程將消耗一天的時(shí)間棱诱,這一天等于白白浪費(fèi)了。因?yàn)槟憬裉觳]有執(zhí)行有意義的接吻工作涝动,雖然你也在馬不停蹄地忙碌迈勋,但那用于移動(dòng)距離、尋找目標(biāo)的忙碌對(duì)完成接吻任務(wù)而言是不必要的醋粟,也是沒有意義的靡菇。這樣的話,你是一天干活米愿,一天用于切換任務(wù)厦凤,即從當(dāng)前對(duì)象跑到另一個(gè)對(duì)象那里,平均下來吗货,兩天只能完成48次的接吻任務(wù)泳唠,這樣的話你需要花費(fèi)200天才能完成DNA上面約定的接吻任務(wù)量,效率比著單對(duì)象模式大打折扣宙搬。
這里只考慮完工作一天之后才切換目標(biāo)的情況笨腥,假如你沒有太多耐心,打一槍立馬就換地方勇垛,即完成一次接吻之后立即就跑到另一個(gè)對(duì)象那里脖母。這樣的話你將消耗4900天的時(shí)間才能完成任務(wù),效率極低闲孤,需要13年多才能完成任務(wù)谆级。
再假設(shè)你有多個(gè)對(duì)象烤礁,你的耐心很少,打一槍換十個(gè)地方肥照,即完成一次接吻之后脚仔,立馬跑到下一個(gè)對(duì)象那里,但發(fā)現(xiàn)這個(gè)對(duì)象沒化妝舆绎、沒打扮鲤脏,很難看,然后再立馬跑到下一個(gè)對(duì)象那里吕朵,平均見十個(gè)對(duì)象才碰到一個(gè)滿意的猎醇,這樣的話你將消耗48100天才能完成任務(wù)全跨,效率更低了熔任,需要131多年才能完成任務(wù),怕是你這輩子不能死而無憾了拭抬,即返回值不能為0了梧税。
在假設(shè)你根本沒有耐心沦疾,打一槍換n個(gè)地方,即完成了一次接吻之后贡蓖,立馬跑到下一個(gè)對(duì)象那里曹鸠,但發(fā)現(xiàn)這個(gè)對(duì)象沒化妝、沒打扮斥铺,很難看彻桃,然后再立馬跑到下一個(gè)對(duì)象那里,接著就不斷地在重復(fù)這種死循環(huán)的奔波狀態(tài)晾蜘,因?yàn)楹翢o耐心邻眷,眼光又很高、很挑剔剔交,這輩子都在尋找合適的對(duì)象用于接吻肆饶,但一直都找不到。這種現(xiàn)象在計(jì)算機(jī)的狀態(tài)切換中被稱為死鎖岖常,死鎖是存在的驯镊,死鎖一旦出現(xiàn),程序就死了竭鞍,不會(huì)再執(zhí)行任務(wù)了板惑,陷入了一直切換狀態(tài)的情形中。這樣的話程序不論跑多久都不能完成任務(wù)偎快,強(qiáng)制退出或者意外中斷冯乘,返回值都不可能為0。
在計(jì)算機(jī)中晒夹,類的對(duì)象又稱為類的實(shí)例裆馒,因此我們把一個(gè)類只生成一個(gè)對(duì)象的模式稱為單例模式姊氓。
以下是常見的幾種創(chuàng)建單例的模式。
說明:我寫的懶漢式與餓漢式和別人的命名剛好是相反的喷好,這個(gè)感覺每個(gè)人的理解不同翔横,叫什么名字無所謂啦,只要能理解思想就行绒窑。
2. 本文地址
- 博客園:https://www.cnblogs.com/coco56/p/11253656.html
- 簡(jiǎn)書:http://www.reibang.com/p/4c47f8e3809b
- CSDN:https://blog.csdn.net/COCO56/article/details/97409050
3. 通過繼承單例父類來實(shí)現(xiàn)
實(shí)測(cè)發(fā)現(xiàn)通過繼承的方法來實(shí)現(xiàn)Python程序的單例模式設(shè)計(jì)是最完美的棕孙,之前嘗試過使用裝飾器來實(shí)現(xiàn)單例模式,但具體實(shí)踐的時(shí)候會(huì)出現(xiàn)諸多問題些膨。
比如如果在裝飾器中添加了getInstance方法和Instance屬性,那主流的IDE或者編輯器將無法推導(dǎo)出來有這個(gè)方法或?qū)傩郧掌蹋M管你的語(yǔ)法是正確的订雾,但由于編輯器無法推導(dǎo)出來,你將在編寫代碼的時(shí)候無法使用tab鍵快速補(bǔ)全矛洞,很不方便洼哎。
自身是為了自身的存在而存在的,用自身的存在去完善和改良自身的存在無疑是最好的選擇沼本。
前面介紹過噩峦,對(duì)象的存在其實(shí)是為了更好地解決繼承的問題的。既然是解決繼承的問題抽兆,那么無疑最好的選擇就是用繼承本身去解決繼承识补。
具體做法為:建一個(gè)單例父類,這個(gè)類只解決單例模式的問題辫红,然后所有需要使用單例模式的類全部繼承自這個(gè)單例父類凭涂。
相當(dāng)于老祖先把單例模式的問題解決了,孩子們只需要繼承老祖先的基因就可以了贴妻,這樣切油,孩子們天生就是單例模式(因?yàn)槔献嫦瘸私鉀Q單例問題,其他什么問題都不去做名惩,相當(dāng)于窮盡畢生的力量將單例問題研究地透透的澎胡,孩子們因?yàn)槔^承自老祖先,本身肯定已經(jīng)保留了老祖先的優(yōu)良基因)娩鹉。
經(jīng)實(shí)際測(cè)試攻谁,這種用繼承本身去解決繼承相關(guān)問題的方法是完美的。
示例代碼:
# -*- coding : utf-8 -*-
import threading
_instances = {}#相當(dāng)于民政局的登記簿底循,用于記錄每個(gè)單例類的專屬對(duì)象
_lock = threading.Lock()#使用線程鎖以確保線程安全
class SingletonClass(object):
global _instances, _lock
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of', cls, *args, **kwargs)
if cls in _instances: return _instances[cls]
_lock.acquire()#上鎖
_instances[cls] = cls._instances = super().__new__(cls)
_lock.release()#解鎖
return _instances[cls]
@classmethod #用于刪除對(duì)象巢株,此方法僅在必要時(shí)使用,因?yàn)槿绻磸?fù)的析構(gòu)和構(gòu)造對(duì)象的話是極其浪費(fèi)資源的熙涤。
def delInstance(cls, *args, **kwargs):
if cls in _instances: _instances[cls].__del__(*args, **kwargs); del _instances[cls]
@classmethod #用于獲取對(duì)象
def getInstance(cls, *args, **kwargs):
if cls in _instances: return _instances[cls]
return cls(*args, **kwargs)
class CopyTree(SingletonClass):
def __init__(self, *args, **kwargs): print('__init__ of', self, *args, **kwargs)
def __del__(self, *args, **kwargs): print('__del__ of', self, *args, **kwargs)
class CopySubTree(CopyTree):
pass
class CopyBCSubTree(CopyTree):
pass
if __name__ == "__main__":
#調(diào)用構(gòu)造函數(shù)和調(diào)用getInstance方法雖然都是獲得的單例阁苞。但對(duì)于已存在的對(duì)象來說:調(diào)用構(gòu)造函數(shù)困檩,會(huì)重新調(diào)用__init__方法再初始化一遍兒此對(duì)象。
cls =CopyTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
cls =CopySubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
cls =CopyBCSubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
print('\nbefore del:\n', _instances); cls.delInstance(); print('after del:\n', _instances);
4. 使用裝飾器實(shí)現(xiàn)
4.1. 懶漢式
懶漢式就是說“國(guó)家”分配對(duì)象那槽,在你還未出生的時(shí)候就已經(jīng)被指腹為婚了悼沿,這樣在你出生的時(shí)候就立即擁有了一個(gè)對(duì)象了,再也不用發(fā)愁對(duì)象的事兒了骚灸,這種不需要自己主動(dòng)付出就能得到對(duì)象的模式被稱為懶漢模式糟趾。
為了方便,這里我是用裝飾器甚牲,對(duì)需要的類進(jìn)行裝飾义郑,達(dá)到了一次定義,以后再處處使用時(shí)只需要一行代碼的效果丈钙。
優(yōu)點(diǎn):省心非驮,開始的時(shí)候一人分一個(gè)對(duì)象就好了,很省事雏赦。
缺點(diǎn):構(gòu)造函數(shù)只能是無參的劫笙,自己有想法,想傳個(gè)參數(shù)的話是不好傳過去的星岗。這是因?yàn)樵谠O(shè)計(jì)時(shí)就沒考慮你有想法的情況下填大,別管三七二十一,上來就給你分一個(gè)對(duì)象俏橘。
def Singleton(cls):
print("正在進(jìn)行裝飾類", cls, '哦~')
cls._instance = cls()#需要傳參數(shù)的話在這里改一下參數(shù)列表允华,但那樣的話就不具備如此廣泛的通用性了,我們想要的效果是一次定義裝飾器敷矫,以后對(duì)所有的類都適用例获。
print('類', cls, '裝飾完畢,并且我們還給它分配了一個(gè)對(duì)象', cls._instance, '\n')
def _singleton(): return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
4.2. 餓漢式
餓漢式是指你餓了才給你分一個(gè)對(duì)象曹仗,把對(duì)象只分配給那些饑渴難耐大漢們(不分的話可能會(huì)出現(xiàn)問題榨汤,畢竟大漢如果發(fā)起情來還是很騷的,恐怕難以招架)怎茫。
優(yōu)點(diǎn):節(jié)省空間收壕,物盡其用,需要時(shí)才給你轨蛤,如果不需要想單身一輩子的話就不給你分配對(duì)象了蜜宪。
缺點(diǎn):可能存在線程不安全,饑渴難耐的大漢如果在分配對(duì)象的時(shí)候一下子多占了多個(gè)不同的對(duì)象怎么辦祥山?
4.2.1. 未加鎖版
一般在初始化對(duì)象的時(shí)候如果不進(jìn)行IO操作圃验,是沒事兒的。(即init方法里沒有IO操作)
這個(gè)版本實(shí)現(xiàn)簡(jiǎn)單缝呕,執(zhí)行速度也快澳窑,不用來回上鎖了斧散。加鎖版的就是給大漢分對(duì)象的時(shí)候先把大漢五花大綁地鎖起來,這樣的話就避免了在分配時(shí)他搶占多個(gè)對(duì)象的可能摊聋。分配完了之后再給大漢松下綁鸡捐、解下鎖,這樣步驟一多麻裁,肯定是比較耗時(shí)的箍镜。
示例1:裝飾時(shí)給每個(gè)類創(chuàng)建一個(gè)_instance屬性
def Singleton(cls):
print("正在進(jìn)行裝飾類", cls, '哦~')
cls._instance = None
def _singleton(*args, **kargs):
if cls._instance: return cls._instance
cls._instance = cls(*args, **kargs)
return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
示例2:裝飾給創(chuàng)建一個(gè)空字典,生成對(duì)象時(shí)再把這個(gè)對(duì)象添加到字典里煎源,下次如果查到字典里有所需對(duì)象的話就直接返回了色迂,沒有的話創(chuàng)建后再返回。
_instance = {}
def Singleton(cls):
print("正在進(jìn)行裝飾類", cls, '哦~')
global _instance
def _singleton(*args, **kargs):
if cls in _instance: return _instance[cls]
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
4.2.2. 加鎖版
import threading
_lock = threading.Lock()
def Singleton(cls):
print("正在進(jìn)行裝飾類", cls, '哦~')
global _lock
cls._instance = None
def _singleton(*args, **kargs):
if cls._instance: return cls._instance
_lock.acquire()#上鎖
cls._instance = cls(*args, **kargs)
_lock.release()#去鎖
return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)