7.對象性能
- 面向?qū)ο蠛芎玫亟鉀Q了“抽象”的問題苗傅,但是必不可免地要付出一定的代價。對于通常情況來講胰挑,面向?qū)ο蟮某杀敬蠖伎梢院雎圆挥嬍远痢5悄承┣闆r杠纵,面向?qū)ο笏鶐淼某杀颈仨氈?jǐn)慎處理。
- 典型模式
- Singleton
- Flyweight
7.1 Singleton
動機(jī)
- 在如那件系統(tǒng)中钩骇,經(jīng)常有這些一些特殊的類比藻,必須保證它們在系統(tǒng)中只存在一個實例,才能確保它們的邏輯正確性倘屹、以及良好的效率银亲。
- 如何繞過常規(guī)的構(gòu)造器,保證一個類只有一個實例唐瀑。
- 這應(yīng)該是類設(shè)計者的責(zé)任群凶,而不是使用者的責(zé)任插爹。
寫法
- 常規(guī)寫法哄辣,線程不安全
Singleton* Singleton::getInstance(){ if(m_instance == nullptr){ m_instance = new Singleton; } return m_instance; }
- 加鎖,但是代價太大
Singleton* Singleton::getInstance(){ Lock lock; if(m_instance == nullptr){ m_instance = new Singleton; } return m_instance; }
- 雙檢查鎖赠尾,但由于內(nèi)存讀寫reorder不安全
Singleton* Singleton::getInstance(){ if(m_instance == nullptr){ Lock lock; if(m_instance == nullptr){ m_instance = new Singleton; } } return m_instance; }
- 在new的過程中力穗,理論上是如下的三個步驟:
- 分配內(nèi)存
- 執(zhí)行構(gòu)造函數(shù)
- 將指針賦值給m_instance
- 但是如果發(fā)生reorder,有可能顛倒順序:
- 分配內(nèi)存
- 將指針賦值給m_instance
- 執(zhí)行構(gòu)造函數(shù)
- 此時如果thread A 按此步驟執(zhí)行气嫁,將指針賦值給了m_instance当窗,而恰恰thread B 來獲取m_instance,由于指針已經(jīng)賦值給m_instance寸宵,那么thread B就會得到m_instance崖面,但是此時thread A還未調(diào)用構(gòu)造函數(shù),m_instance指向的對象還不能用梯影,如果這個時候thread B去使用他得到的m_instance巫员,就會出問題。
- 在new的過程中力穗,理論上是如下的三個步驟:
- C++ 11 版本之后的跨平臺實現(xiàn)
std::atomic<Singleton*> Singleton::m_instance; std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance(){ Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); //獲取內(nèi)存fence if(tmp == nullptr){ std::lock_guard<std::mutex> lock(m_mutex); if(tmp == nullptr){ tmp = new Singleton; m_instance = new Singleton(); std::atomic_thread_fence(std::memory_order_release); //釋放內(nèi)存fence m_instance.store(tmp, std::memory_order_relaxed); } } return tmp; }
模式定義
- 保證一個類只有一個實例甲棍,并提供一個該實例的全局訪問點(diǎn)简识。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Singleton模式中的實例構(gòu)造器可以設(shè)置為protected以允許子類派生。
- Singleton模式一般不要支持拷貝構(gòu)造函數(shù)和Clone接口感猛,這會導(dǎo)致多個對象七扰,違背了Singleton模式的初衷。
- 多線程安全的Singleton陪白,應(yīng)該注意雙檢查鎖的正確實現(xiàn)颈走。
7.2 Flyweight
動機(jī)
- 在軟件系統(tǒng)采用純粹對象方案的問題在于大量細(xì)粒度的對象會很快充斥在系統(tǒng)中,從而帶來很高的運(yùn)行時代價————主要指內(nèi)存需求方面的代價咱士。
- 如何在避免大量細(xì)粒度對象問題的同時立由,讓外部客戶程序仍然能夠透明地使用面向?qū)ο蟮姆绞絹磉M(jìn)行操作袖瞻?
模式定義
- 運(yùn)用共享技術(shù)(解決效率問題的常用手段)有效地支持大量細(xì)粒度的對象。
結(jié)構(gòu)
- GetFlyweight(key) 通過這個方法得到對象拆吆,函數(shù)通過key進(jìn)行判斷聋迎,如果存在,直接返回枣耀,如果不存在霉晕,則創(chuàng)建,并加入pool中捞奕,再返回牺堰。
要點(diǎn)總結(jié)
- 面向?qū)ο蠛芎玫亟鉀Q了抽象性的問題,但是作為一個運(yùn)行在機(jī)器中的程序?qū)嶓w颅围,我們需要考慮對象的代價問題伟葫。Flyweight主要用來解決代價問題,一般不觸及面向?qū)ο蟮某橄笮詥栴}院促。
- Flyweight采用對象共享的做法來降低系統(tǒng)中對象的個數(shù)筏养,從而降低細(xì)粒度對象給系統(tǒng)帶來的內(nèi)存壓力。在具體實現(xiàn)方面常拓,要注意對象狀態(tài)的處理渐溶。
- 對象數(shù)量太大從而導(dǎo)致對象內(nèi)存開銷加大————什么樣的數(shù)量才算大?這需要我們根據(jù)具體的應(yīng)用情況去評估弄抬。
8.狀態(tài)變化
- 在組建構(gòu)建過程中茎辐,某些對象的狀態(tài)經(jīng)常面臨變化,如何對這些變化進(jìn)行有效的管理掂恕?同時又維持高層模塊的穩(wěn)定拖陆? 狀態(tài)變化模式為這一問題提供了一種解決方案。
- 典型模式
- State
- Memento
8.1 State
動機(jī)
- 在軟件構(gòu)建過程中懊亡,某些對象的狀態(tài)如果改變依啰,其行為也會隨之而發(fā)生改變,比如文檔處于只讀狀態(tài)斋配,其支持的行為和讀寫狀態(tài)支持的行為就可能完全不同孔飒。
模式定義
- 允許一個對象在內(nèi)部狀態(tài)改變時改變它的行為,從而使對象看起來似乎修改了其行為艰争。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- State模式將所有與一個特定狀態(tài)相關(guān)的行為都放入一個State的子類對象中坏瞄,在對象狀態(tài)切換時、切換相應(yīng)的對象甩卓;但同時維持State的接口鸠匀,這樣實現(xiàn)了具體操作與狀態(tài)裝換之間的解耦。
- 為不同的狀態(tài)引入不同的對象使得狀態(tài)轉(zhuǎn)換變得更加明確逾柿,而且可以保證不會出現(xiàn)狀態(tài)不一致的情況缀棍,因為轉(zhuǎn)換時原子性的----即要么徹底轉(zhuǎn)換過來宅此,要么不轉(zhuǎn)換。
- 如果Stat對象沒有實例變量爬范,那么各個上下文可以共享同一個State對象父腕,從容節(jié)省對象開銷。
8.2 Memento
動機(jī)
- 在軟件構(gòu)建過程中青瀑,某些對象的狀態(tài)在轉(zhuǎn)換過程中璧亮,可能由于某種需要,要求程序能夠回溯到對象之前處于某個點(diǎn)時的狀態(tài)斥难。如果使用一些共有接口來讓其他對象得到對象的狀態(tài)枝嘶,便會暴露對象的實現(xiàn)細(xì)節(jié)。
模式定義
- 在不破壞封裝性的前提下哑诊,捕獲一個對象的內(nèi)部狀態(tài)群扶,并在該對象之外保存這個狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)镀裤。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- 備忘錄(Memento)存儲原發(fā)器(Originator)對象的內(nèi)部狀態(tài)竞阐,在需要時恢復(fù)原發(fā)器狀態(tài)。
- Memento模式的核心是信息隱藏淹禾,即Originator需要向外接隱藏信息馁菜,保持其封裝性。但同時又需要將狀態(tài)保持到外界(Memento)铃岔。
- 由于現(xiàn)代語言運(yùn)行時(如C#、Java等)都具有相當(dāng)?shù)膶ο笮蛄谢С智突穑虼送捎眯矢呋傧啊⒂秩菀渍_實現(xiàn)的序列化方案來實現(xiàn)Memento模式。
9.數(shù)據(jù)結(jié)構(gòu)
- 常常有一些組件在內(nèi)部具有特定的數(shù)據(jù)結(jié)構(gòu)卖丸,如果讓客戶程序依賴這些特定的數(shù)據(jù)結(jié)構(gòu)纺且,將極大的破壞組件的復(fù)用。這時候稍浆,將這些特定數(shù)據(jù)結(jié)構(gòu)封裝在內(nèi)部载碌,在外部提供統(tǒng)一的接口,來實現(xiàn)與特定數(shù)據(jù)結(jié)構(gòu)無關(guān)的訪問衅枫。
- 典型模式
- Composite
- Iterator
- Chain Of Resposibility
9.1 Composite
動機(jī)
- 客戶代碼過多地依賴于對象容器復(fù)雜的內(nèi)部實現(xiàn)結(jié)構(gòu)嫁艇,對象容器內(nèi)部實現(xiàn)結(jié)構(gòu)(而非抽象接口)的變化將引起客戶代碼的頻繁變化,帶來了代碼的維護(hù)性弦撩、擴(kuò)展性等弊端步咪。
模式定義
- 將對象組合成樹形結(jié)構(gòu)以表示部分-整體的層次結(jié)構(gòu)。Composite使得用戶對單個對象和組合對象的使用具有一致性(穩(wěn)定)益楼。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Composite模式采用樹形結(jié)構(gòu)來實現(xiàn)普遍存在的對象容器猾漫,從而將一對多的關(guān)系轉(zhuǎn)換成一對一的關(guān)系点晴,使得客戶程序可以一致地處理對象和對象容器,無需關(guān)心處理的是單個的對象悯周,還是組合的對象容器粒督。
- 將客戶代碼與復(fù)雜的對象容器結(jié)構(gòu)解耦是Composite的核心思想,解耦之后禽翼,客戶代碼將與純粹的抽象接口----而非對象容器的內(nèi)部實現(xiàn)結(jié)構(gòu)----發(fā)生依賴坠陈,從而更能應(yīng)對變化。
- Composite模式在具體實現(xiàn)中捐康,可以讓父對象中的子對象反向追溯仇矾;如果父對象有頻繁的遍歷要求,可使用緩存技巧來改善效率解总。
9.2 Iterator
動機(jī)
- 集合對象內(nèi)部結(jié)構(gòu)常常變化各異贮匕。但對于這些集合對象,我們希望在不暴露其內(nèi)部結(jié)構(gòu)的同事花枫,可以讓外部客戶代碼透明地訪問其中包含的元素刻盐。同時這種透明遍歷也為同一種算法在多種集合對象上進(jìn)行操作提供了可能。
模式定義
- 提供一種方法順序訪問一個聚合對象中的各個元素劳翰,而又不暴露(隔離變化敦锌,穩(wěn)定)該對象的內(nèi)部表示。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- 迭代抽象:訪問一個聚合對象的內(nèi)容而無需暴露它的內(nèi)部表示佳簸。
- 迭代多態(tài):為遍歷不同的集合結(jié)構(gòu)提供一個統(tǒng)一的接口乙墙,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作。
- 迭代器的健壯性考慮:遍歷的同時更改迭代器所在的集合結(jié)構(gòu)生均,會導(dǎo)致問題听想。
9.3 Chain Of Resposibility
動機(jī)
- 一個請求可能被多個對象處理,但是每個請求在運(yùn)行時只能有一個接受者马胧,如果顯示指定汉买,將必不可少地帶來請求發(fā)送者與接受者的緊耦合。
模式定義
- 使多個對象都有機(jī)會處理請求佩脊,從而避免請求的發(fā)送者和接受者之間的耦合關(guān)系蛙粘。將這些對象連成一條鏈,并沿著這條鏈傳遞請求威彰,直到有一個對象處理它為止出牧。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Chain Of Responsibility 模式的應(yīng)用場合在于一個請求可能有多個接受者,但是最后真正的接受者只有一個抱冷,這個時候請求發(fā)送者與接受者的耦合有可能出現(xiàn)變化脆弱的癥狀崔列,職責(zé)鏈的目的就是講二者解耦,從而更好地應(yīng)對變化。
- 應(yīng)用了Chain Of Responsibility模式后赵讯,對象的責(zé)任分派將更具靈活定盈咳,我們可以在運(yùn)行時添加/修改請求的處理職責(zé)。
- 如果請求傳遞到職責(zé)鏈的末尾扔得不到處理边翼,應(yīng)該有一個合理的缺省機(jī)制鱼响,這也是每一個接受對象的責(zé)任,而不是發(fā)出請求的對象的責(zé)任组底。
9.4 回顧與總結(jié)
- 以上三個模式的特征很明顯丈积,都是和數(shù)據(jù)結(jié)構(gòu)有關(guān)。
- 歸到一起债鸡,能夠從認(rèn)知上很快的辨析出來其共性江滨,但是Iterator模式和Chain Of Responsibility模式在現(xiàn)有軟件環(huán)境下已經(jīng)過時。
10.行為變化
- 在組件的構(gòu)建過程中厌均,組件行為的變化經(jīng)常導(dǎo)致組件本身劇烈的變化唬滑,行為變化模式將組件的行為和組件本身進(jìn)行解耦,從而支持組件的行為變化棺弊,實現(xiàn)兩者之間的松耦合晶密。
- 典型模式
- Command
- Visitor
10.1 Command
動機(jī)
- 行為請求者與行為實現(xiàn)者通常呈現(xiàn)一種緊耦合。但在某些場合----比如需要對行為進(jìn)行記錄模她、撤銷/重做稻艰、事務(wù)等處理,這種無法抵御變化的緊耦合使不合適的侈净。
模式定義
- 將一個請求(行為)封裝為一個對象尊勿,從而使你可用不同的請求對客戶進(jìn)行參數(shù)化;對請求排隊或記錄請求日志用狱,以及支持可撤銷的操作运怖。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Command模式的根本弟弟在于將行為請求者與行為實現(xiàn)者解耦,在面向?qū)ο笳Z言中夏伊,常見的手段是將行為抽象為對象。
- 實現(xiàn)Command接口的具體命令對象ConcreteCommand有時候根據(jù)需要可能會保存一些額外的信息吻氧。通過使用Composite模式溺忧,可以講多個命令封裝為一個符合命令MacroCommand。
- Command模式與C++中的函數(shù)對象有些類似盯孙,但兩者定義行為接口的規(guī)范有所區(qū)別:Command以面向?qū)ο笾械?strong>接口--實現(xiàn)來定義行為接口說明鲁森,更嚴(yán)格,但有性能損失振惰;C++函數(shù)對象以函數(shù)名來定義行為接口規(guī)范歌溉,更靈活,性能更高。
10.2 Visitor
動機(jī)
- 在軟件構(gòu)建過程中痛垛,由于需求的改變草慧,某些類層次結(jié)構(gòu)中常常需要增加新的行為(方法),如果直接在積累中做這樣的改變匙头,將會給予子類帶來很繁重的變更負(fù)擔(dān)漫谷,甚至破壞原有設(shè)計。
模式定義
- 表示一個作用于某種對象結(jié)構(gòu)中的各元素的操作蹂析。使得可以在不改變(穩(wěn)定)各元素的類的前提下定義(擴(kuò)展)作用于這些元素的新操作(變化)舔示。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Visitor模式通過所謂的雙重分發(fā)(double dispatch)來實現(xiàn)在不更改(不添加新的操作--編譯時)Element類層次結(jié)構(gòu)的前提下,在運(yùn)行時透明地為類層次結(jié)構(gòu)上的各個類動態(tài)添加新的操作(支持變化)电抚。
- 所謂雙重分發(fā)即Visitor模式中間包括了兩個多態(tài)分發(fā)(注意其中的多態(tài)機(jī)制):第一個為accept方法的多態(tài)解析惕稻;第二個為visitElementX方法的多態(tài)辨析。
- Visitor模式的最大缺點(diǎn)在于擴(kuò)展類層次結(jié)構(gòu)(添加新的Element子類)蝙叛,會導(dǎo)致Visitor類的改變俺祠。因此Visitor模式適用于Element類層次結(jié)構(gòu)穩(wěn)定,而其中的操作卻經(jīng)常面臨頻繁改動甥温。
11.領(lǐng)域規(guī)則
- 在特定領(lǐng)域中锻煌,某些變化雖然頻繁,但可以抽象為某種規(guī)則姻蚓。這時候宋梧,結(jié)合特定領(lǐng)域,將問題抽象為語法規(guī)則狰挡,從而給出在該領(lǐng)域下的一般性解決方案捂龄。
- 典型模式
- Interpreter
Interpreter
動機(jī)
- 在軟件構(gòu)建過程中,如果某一特定領(lǐng)域的問題比較復(fù)雜加叁,類似的結(jié)構(gòu)不斷重復(fù)出現(xiàn)倦沧,如果使用普通的編程方式來實現(xiàn)將面臨非常頻繁的變化。
模式定義
- 給定一個語言它匕,定義它的文法的一種表示展融,并定義一種解釋器,這個監(jiān)視器使用該表示來解釋語言中的句子豫柬。
結(jié)構(gòu)
要點(diǎn)總結(jié)
- Interpreter模式的應(yīng)用場合是Interprete模式應(yīng)用中的難點(diǎn)告希,只有滿足業(yè)務(wù)規(guī)則頻繁變化,且類似的結(jié)構(gòu)不斷重復(fù)出現(xiàn)烧给,并且容易抽象為語法規(guī)則的問題燕偶,才適合使用Interpreter模式。
- 使用Interpreter模式來表示文法規(guī)則础嫡,從而可以使用面向?qū)ο蠹记蓙矸奖愕財U(kuò)展文法指么。
- Interpreter模式比較適合簡單的文法表示,對于復(fù)雜的文發(fā)表示,Interpreter模式會產(chǎn)生比較大的類層次結(jié)構(gòu)伯诬,需要求助于語法分析生成器這樣的標(biāo)準(zhǔn)工具晚唇。