Week11 Notes
“對象性能模式”
單間模式
面向?qū)ο蠛芎玫亟鉀Q了抽象的問題,但是不可避免要付出一定的代價,在某些情況下,抽象帶來的代價需要謹(jǐn)慎處理,比如虛函數(shù)和繼承
虛函數(shù)帶來的代價還是很多的,某些倍乘效應(yīng)
典型的模式有Singleton和Flyweight
在某些特殊的場景下,面向?qū)ο髱淼某杀居袉栴}
經(jīng)常有一些特殊的類,必須保證在系統(tǒng)中只存在一個實(shí)例,才能確保邏輯的正確,以及具有良好的效率,如何繞過常規(guī)的構(gòu)造器使一個類只有一個實(shí)例呢,這是設(shè)計者的責(zé)任,不是使用者的責(zé)任
怎么實(shí)現(xiàn)呢?
Class Singleton{
Private:
? ? Singleton();
? ? Singleton(constSigleton& other);
Public:
? ? StaticSingleton* getInstance();
? ? StaticSingleton* m_instance;
};
//線程非安全版本
Singleton* Singleton::m_instance = nullptr;
Singleton* Singleton::getInstace(){
? ? If(m_instace== nullptr){
? ? ? ? M_instance = new Singleton();
? ? }
? ? returnm_instance;
}
//線程安全版本。但鎖的代價過高
Singleton* Singleton::getInstance(){
? ? Locklock;
? ? If(m_instance== nullptr){
? ? ? ? M_instance= new Singleton();
? ? }
? ? returnm_instance;
}
這是線程安全版本,鎖的代價在哪里?假設(shè)對象已經(jīng)被創(chuàng)建出來,m_instance已經(jīng)不是null了,這是后兩個線程都是在讀,此時不需要加鎖。
當(dāng)高并發(fā)的時候(外部服務(wù)器)十萬人同時在線,在這種場景下,這個鎖的代價可能也是很高的。
//雙檢查鎖,但由于內(nèi)存讀寫reorder不安全
Singleton* Singleton::getIncetance(){
? ? If(m_instance== nullptr){
? ? ? ? Locklock;
? ? ? ? If(m_instance == nullptr)
? ? ? ? M_instance = new Singleton();
? ? }
? ? returnm_instance;
}
鎖前檢查,正確但代價過高
鎖后檢查,為了保證正確
內(nèi)存讀寫reorder的情況會導(dǎo)致雙檢查鎖失效
在指令層的時候和我們的假設(shè)有可能不一樣
new的這一步先分配內(nèi)存,再調(diào)用構(gòu)造器,一般對分配的那塊內(nèi)存初始化,第三步是做完的這些東西的指針值給m_instance,但這三步是我們假想的順序,在指令層,這三步有可能被reorder,先分配內(nèi)存,然后把內(nèi)存地址給m_instance,最后再調(diào)用構(gòu)造器。這是編譯器優(yōu)化的時候有可能會這樣干,每一種語言都會有這種情況。怎么解決這個問題?
//C++ 11版本以后的跨平臺實(shí)現(xiàn)(volatile)
std::atomicSingleton::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);
? ? If(tmp== nullptr){
? ? ? ? Std::lock_guardlock(m_mutex);
? ? ? ? Tmp= m_instance.load(std::memory_order_relaxed);
? ? ? ? If(tmp== nullptr){
? ? ? ? ? ? Tmp= new Singleton;
? ? ? ? ? ? Std::atomic_thread_fence(std::memory_order_relaxed);
? ? ? ? ? ? M_instance.store(tmp,std::memory_order_relaxed);
? ? ? ? }
? ? }
? ? return tmp;
}
作用是保證一個類僅有一個實(shí)例,實(shí)例構(gòu)造器可以設(shè)置為protected以允許子類派生,一般不要支持拷貝構(gòu)造函數(shù)和clone接口,因?yàn)檫@樣有可能導(dǎo)致多個對象實(shí)例。
享元模式
Flyweight
在軟件系統(tǒng)中采用純粹對象方案的問題在于大量細(xì)粒度的對象會很快充斥在系統(tǒng)中,從而帶來很高的運(yùn)行時代價。使用共享的技術(shù)可以有效地支持大量細(xì)粒度的對象
class Font{
private:
? ? //uniqueobject key
? ? stringkey;
public:
? ? Font(conststring& key){…}
};
class FontFactory{
private:
? ? map fontPool;
public:
? ? Font*GetFont(const string& key){
? ? ? ? Map::iterator item = fontPool.find(key);
? ? ? ? If(item!=footPool.end()){
? ? ? ? ? ? ReturnfontPool[key];
? ? ? ? }
? ? ? ? else{
? ? ? ? ? ? Font* font = new Font(key);
? ? ? ? ? ? fontPool[key] = font;
? ? ? ? ? ? return font;
? ? ? ? } ?
? ? }
void clear(){…}
}
對象池的一種設(shè)計方式,有就返回,沒有就創(chuàng)建一個。實(shí)際中數(shù)據(jù)結(jié)構(gòu)也不同,接口也可能不同。通過一個共享的方案來支持大量細(xì)粒度的對象。出去的對象最好是只讀的,否則出去以后被更改了。Flyweight一般只解決對象的代價問題。對象的數(shù)量太大導(dǎo)致對象的開銷太大。對象上10萬,大概是5M的水平
幾十個對象不要用flyweight,里面使用的數(shù)據(jù)結(jié)構(gòu)也一樣需要開銷
狀態(tài)模式
“狀態(tài)變化模式”某些對象的狀態(tài)經(jīng)常面臨變化,如何對這些變化進(jìn)行有效的管理,同時又維持高層模塊的穩(wěn)定?某些對象的狀態(tài)如果改變,其行為也會發(fā)生變化,比如文檔處于只讀狀態(tài)。
Enum NetworkState{
? ? Network_Open
? ? Network_Close,
? ? NetworkConnect,
};
//類似于狀態(tài)機(jī)
class NetworkProcessor{
? ? NetworkStatestate;
Public:
? ? VoidOperation1(){
? ? ? ? If(state== Network_Open){
? ? ? ? ? ? State= Network_Close;
? ? ? ? }
? ? ? ? else_if …
? ? }
? ? voidoperation2(){…}
}
那我們這樣寫有什么問題?
這和之前的strategy很像,if_else在你的代碼中出現(xiàn)很多,以后如果添加了一種新的狀態(tài)wait,那之前的代碼要怎么更改,需求的變化讓你的代碼不斷地在變化,按照strategy給我們的經(jīng)驗(yàn),先提抽象基類
class NetworkState{
public:
? ? Network*pNext;
? ? Virtualvoid Operation1() = 0;
? ? Virtualvoid Operation2() = 0;
? ? Virtual~NetworkState(){}
};
class OpenState:public NetworkState{
? ? staticNetworkState* m_instance;
public:
? ? staticNetworkState* getInstance(){
? ? ? ? if(m_instance== nulptr){
? ? ? ? ? ? m_instance= new OpenState();
? ? ? ? }
? ? ? ? return m_instance;
? ? }
? ? voidoperation1(){
? ? ? ? //…
? ? ? ? pNext = CloseState::getInstance; ?
? ? }
};
class NetworkProcessor{
? ? NetworkState*pState;
Public:
? ? NetworkProcessor(NetworkState*pState){
? ? ? ? This->pState= pState;
? ? }
? ? voidOperation1(){
? ? ? ? pState->Operation1();
? ? ? ? pState= pState->pNext;
? ? }
};
允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為,從而是對象看起來似乎修改了其行為。和strategy非常像。為不同的狀態(tài)引入不同的對象使得狀態(tài)轉(zhuǎn)換變得更加明確,如果state對象沒有實(shí)例變量,那么各個上下文可以共享一個state對象,從而節(jié)省對象開銷。(Singleton設(shè)計模式)
備忘錄
Memento
屬于“狀態(tài)變化”模式,某些對象在狀態(tài)轉(zhuǎn)換過程中,要求程序能夠回溯到對象之前處于某個點(diǎn)時的狀態(tài),如果使用一些公共接口來讓其他對象得到對象的狀態(tài),會暴露對象細(xì)節(jié)實(shí)現(xiàn)
在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在對象外部保存這個狀態(tài)。
Class Memento{
? ? Stringstate;
Public;
? ? Memento(conststring& s): state(s){}
? ? StringgetState() const{return state;}
? ? VoidsetState(const string& s) {state = s;}
?}
class Originator{
? ? stringstate;
public:
? ? Originator(){}
? ? MementocreateMomento(){
? ? ? ? Mementom(state);
? ? ? ? Returnm;
? ? }
? ? voidsetMomento(const Memento& m){
? ? ? ? state = m.getState();
? ? }
};
int main(){
? ? Originatororiginator;
? ? //存儲到備忘錄
? ? Mementomem = originator.reateMomento();//捕獲對象狀態(tài)
? ? //改變originator狀態(tài) ??
? ? //從備忘錄中恢復(fù)
? ? originator.setMomento(memento);?
}
不破壞originator的封裝性,
備忘錄存儲原發(fā)器對象的內(nèi)部狀態(tài),在需要時恢復(fù)原發(fā)器的狀態(tài)。
Memento模式的核心是信息隱藏,即Originator需要向外界隱藏信息,保持其封裝性,但同時又需要將狀態(tài)保持到外界。
由于現(xiàn)代語言運(yùn)行時都具有相當(dāng)?shù)膶ο笮蛄谢С?,因此往往采用效率較高又容易正確實(shí)現(xiàn)的序列化方案來實(shí)現(xiàn)momento模式
實(shí)現(xiàn)的是一個類似于深拷貝的事情,內(nèi)存快照的方式,復(fù)雜對象不好實(shí)現(xiàn),一個對象里面沒有指針,很簡單,但如果指針里面再有指針,指針里面再有指針,這樣具體實(shí)現(xiàn)的時候不太好實(shí)現(xiàn),所以我們一般用序列化,
組合模式
Composite
數(shù)據(jù)結(jié)構(gòu)模式
iterator,chain ofresponsibility
組件在內(nèi)部具有特定的數(shù)據(jù)結(jié)構(gòu),如果讓客戶程序依賴這些特定的數(shù)據(jù)結(jié)構(gòu),會破壞組件的復(fù)用。
需要將客戶代碼和復(fù)雜的對象容器結(jié)構(gòu)解耦。
將對象組合成樹性結(jié)構(gòu)以表示部分和張體的層次結(jié)構(gòu),composite使得用戶對單個對象和組合對象的使用具有一致性。
Class component{
Public:
? ? Virtualvoid process() = 0;
? ? Virtual~component(){}
};
class Composite: public component{
? ? stringname;
? ? listelements;
public:
? ? composite(conststring& s): name(s){}
? ? voidadd(component* element){ ?
? ? ? ? elements.push_back(element);
? ? }
? ? voidremove(component* element){
? ? ? ? elements.remove(element); ?
? ? }
? ? void process(){
? ? ? ? //1.process current node
? ? ? ? //2.process leaf nodes
? ? ? ? for(auto &e:elements)
? ? ? ? ? ? e->process();//多態(tài)調(diào)用
? ? }
};
class Leaf:public component{
? ? stringname;
public:
? ? Leaf(strings): name(s) {}
? ? Voidprocess(){
? ? ? ? //processcurrent node
? ? }
};
void Invoke(component& c){
? ? c.process();
}
int main(){
? ? compositeroot(“root”);?
? ? compositetreeNode1(“treeNode1”);
? ? compositetreeNode2(“treeNode2”)
? ? …
? ? Leafleaf1(“l(fā)eft1”);
? ? Leafleaf2(“l(fā)eft2”);
? ? Root.add(&treeNode1);
? ? treeNode1.add(&treeNode2);
? ? treeNode2.add(&leaf1);?
? ? root.add(&treeNode2);?
}
composite模式采用樹形結(jié)構(gòu)來實(shí)現(xiàn)普遍存在的對象容器,將一對多的關(guān)系轉(zhuǎn)化為一對一的關(guān)系,使得客戶代碼可以一致地處理對象和容器,不需要關(guān)心處理的是單個的兌現(xiàn)還是組合的對象容器,將客戶代碼與復(fù)雜的對象容器結(jié)構(gòu)解耦是composite的核心思想,解耦以后客戶代碼將與純粹的抽象接口而不是對象容器的內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)發(fā)生以來,更能應(yīng)對變化。
迭代器模式
iterator
集合對象內(nèi)部結(jié)構(gòu)常常變化各異,但對于這些集合對象希望不暴露內(nèi)部結(jié)構(gòu)的同事,可以讓外部客戶代碼透明地訪問其中包含的元素,這種透明遍歷也為同一種算法在多種集合對象上進(jìn)行操作提供了可能。
提供一種方法順序訪問一個聚合對象中的各個元素,不暴露該對象的內(nèi)部表示。
職責(zé)鏈
chain of responsibility
一個請求可能被多個對象處理,但每個請求在運(yùn)行的時候只能有一個接受者2,如果顯示指定會有緊耦合
讓請求的需求者自動來判斷
命令模式
command
“行為變化”模式
組件行為的變化經(jīng)常導(dǎo)致組件本身劇烈的變化
運(yùn)行時綁定,一串虛函數(shù)的調(diào)用
如何將行為請求者與行為實(shí)現(xiàn)者解耦?
將請求封裝成一個對象,從而可以用不同的請求對客戶進(jìn)行參數(shù)化,對請求排隊(duì)或記錄請求日志。
一旦把行為作為對象后可能獲得的好處有redo, undo, stack入棧出棧,將行為抽象為對象
實(shí)現(xiàn)command接口的具體命令對象ConcreteCommand有時候需要可能會保存一些額外的狀態(tài)信息。使用composite模式封裝為一個復(fù)合指令
使用接口和實(shí)現(xiàn)來定義行為接口規(guī)范。
訪問器
類層次結(jié)構(gòu)中要增加新的行為方法,在基類中更改會給子類帶來負(fù)擔(dān),所以要透明的為類層次結(jié)構(gòu)上動態(tài)地增加。(運(yùn)行時增加)
class element{
? ? virtualaccept(Visitor &visitor)
}
class visitot{
public:
? ? voidvisitElementA(ElementA& element)
}
訪問器的缺點(diǎn):
有幾個子類visit里就有幾個visitconcreteelemntA
所以需要知道有幾個子類
所以visitor模式需要在類的層次結(jié)構(gòu)需要穩(wěn)定
但其中的操作經(jīng)常面臨頻繁改動
解析器
問題比較復(fù)雜,如果使用
a+b-c+d把規(guī)則提取出來,
class Expression{
public:
? ? virtualint interpreter(map var) = 0;
? ? virtual~Expression(){}
};
變量表達(dá)式:
VarExpression:public Expression{
? ? ?Char key
Public:
? ? VarExpression(constchar& key){
? ? This->key= key;
? ? } ?
? ? int interpreter(map var) override{
? ? ? ? return var[key];
? ? }
};
符號表達(dá)式:
class SymbolExpression:public Expression{
protected:
? ? Expression*left;
? ? Expression*right;
Public:
? ? SymbolExpression(Expression*left, Expression* right):
? ? Left(left),right(right){}
};
class AddExpression: public SymbolExpression{
public:
? ? AddExpression(Expression*left, Expression* right):
? ? ?SymbolExpression(left,right){}
? ? Intinterpreter(map var) override{
? ? ? ? Returnleft->interpreter(var) + right->interpreter(var);?
? ? }
};
Expression* analyse(string expStr){
? ? StackexpStack;
? ? Expression*left = nullptr;
? ? ?Expression*right = nullptr;
? ? For(int I = 0; I< expStr.size(); i++){
? ? ? ? Switch(expStr[i]){
? ? ? ? ? ? Case’+’:
? ? ? ? ? ? Case’-’:
? ? ? ? ? ? Default:
? ? ? ? }
? ? }
}
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,
應(yīng)用的場合是一個難點(diǎn),只有滿足“業(yè)務(wù)規(guī)則頻繁變化,且類似的結(jié)構(gòu)不斷重復(fù)出現(xiàn),并且容易抽象為語法規(guī)則的問題”
使用interpreter模式來表示文法規(guī)則,從而可以使用面向?qū)ο蠹记蓙矸奖銛U(kuò)展
interpreter模式適合簡單的文法表示,復(fù)雜的表示方法會產(chǎn)生比較大的類層次結(jié)構(gòu),需要求助于語法分析生成器這樣的標(biāo)準(zhǔn)工具
總結(jié)
管理變化,提高復(fù)用!
分解和抽象
八大原則
重構(gòu)技巧:
靜態(tài)轉(zhuǎn)為動態(tài),早綁定轉(zhuǎn)為晚綁定,編譯時依賴轉(zhuǎn)為運(yùn)行時依賴,繼承轉(zhuǎn)為組合
直接組合一個對象和繼承在C++內(nèi)存上是一樣的,這樣B就不能變化了,但是如果A中組合B的指針,B就具有靈活性,可以改變,指向B的子類
關(guān)注抽象類和接口,理清變化點(diǎn)和穩(wěn)定點(diǎn),審視依賴關(guān)系,要有framework和application的區(qū)隔思維,良好的設(shè)計是演化的結(jié)果。