“數(shù)據(jù)結(jié)構(gòu)”模式
常常有一些組建在內(nèi)部具有特定的數(shù)據(jù)結(jié)構(gòu)溢豆,如果讓客戶程序依賴這些特定的數(shù)據(jù)結(jié)構(gòu),將極大的破壞組件的復用瘸羡。這時候漩仙,將這些數(shù)據(jù)結(jié)構(gòu)封裝在內(nèi)部,在外部提供統(tǒng)一的接口犹赖,來實現(xiàn)與特定數(shù)據(jù)結(jié)構(gòu)無關(guān)的訪問队他,是一種行之有效的解決方案。
- 典型模式
- Composite
- Iterator
- Chain of Responsibility
組合模式(Composite)
將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層級結(jié)構(gòu)峻村。Compisite使得用戶對單個對象和組合對象的使用具有一致性(穩(wěn)定)麸折。
——《設計模式》GoF
- 動機
在某些軟件情況下,客戶代碼過多地依賴于對像容器復雜的內(nèi)部實現(xiàn)結(jié)構(gòu)粘昨,對像容器內(nèi)部實現(xiàn)結(jié)構(gòu)(而非抽象接口)的變化將因其客戶代碼的頻繁變化垢啼,帶來了代碼的維護性、擴展性等弊端张肾。
#include <iostream>
#include <list>
#include <string>
#include <algorithm>
using namespace std;
class Component
{
public:
virtual void process() = 0;
virtual ~Component(){}
};
//樹節(jié)點
class Composite : public Component{
string name;
list<Component*> elements;//子節(jié)點
public:
Composite(const string & s) : name(s) {}
void add(Component* element) {
elements.push_back(element);
}
void remove(Component* element){
elements.remove(element);
}
void process(){
//核心部分芭析,即處理了當前節(jié)點,也處理了他的所有的子節(jié)點吞瞪,動態(tài)的實現(xiàn)了類似迭代遍歷的方法
//1. process current node處理當前節(jié)點
//2. process leaf nodes處理葉子節(jié)點
for (auto &e : elements)
e->process(); //多態(tài)調(diào)用
}
};
//葉子節(jié)點
class Leaf : public Component{
string name;
public:
Leaf(string s) : name(s) {}
void process(){
//process current node
}
};
//客戶程序
void Invoke(Component & c){
//由于之前已經(jīng)遍歷馁启,此處無需手動來分別處理,對數(shù)據(jù)結(jié)構(gòu)進行了封裝芍秆。
//...
c.process();//多態(tài)調(diào)用
//...
}
int main()
{
Composite root("root");
Composite treeNode1("treeNode1");
Composite treeNode2("treeNode2");
Composite treeNode3("treeNode3");
Composite treeNode4("treeNode4");
Leaf leat1("left1");
Leaf leat2("left2");
root.add(&treeNode1);
treeNode1.add(&treeNode2);
treeNode2.add(&leaf1);
root.add(&treeNode3);
treeNode3.add(&treeNode4);
treeNode4.add(&leaf2);
process(root);
process(leaf2);
process(treeNode3);
}
對于Add惯疙、Remove函數(shù)來說翠勉,放在父類還是子節(jié)點中,有一定的爭議螟碎,因為如果在父類中眉菱,那么對于leaf節(jié)點來說,邏輯上就不應該有Add掉分、Remove方法俭缓。
要點總結(jié)
Composite模式采用樹形結(jié)構(gòu)來實現(xiàn)普遍存在的對像容器,從而將“一對多”的關(guān)系轉(zhuǎn)化為“一對一”的關(guān)系酥郭,使得客戶代碼可以一致的(復用)處理對象和對象容器华坦,無需關(guān)心處理的是單個對象還是組合的對象容器
將“客戶代碼與復雜的對象容器結(jié)構(gòu)”解耦是Composite的核心思想,解耦之后不从,客戶代碼將與純粹的抽象接口——而非對像容器的內(nèi)部實現(xiàn)結(jié)構(gòu)——發(fā)生依賴惜姐,從而更能“應對變化”。
Composite模式在具體實現(xiàn)中椿息,可以讓父對象中的子對象反向追溯歹袁;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率寝优。
迭代器(Iterator)
提供一種方法順序訪問一個聚合對象中的各個元素条舔,而又不暴露(隔離變化,穩(wěn)定)該對象的內(nèi)部表示乏矾。
——《設計模式》GoF
- 動機
在軟件構(gòu)建中孟抗,集合對象內(nèi)部結(jié)構(gòu)常常變化各異。但由于這些集合對像钻心,我們希望在不暴露其內(nèi)部結(jié)構(gòu)的同時凄硼,可以讓外部客戶代碼透明的訪問其中包含的元素;同時這種“透明遍歷”也為“同一種算法在多種集合對象上進行操作”提供了可能捷沸。
使用面向?qū)ο蠹夹g(shù)將這種遍歷機制抽象為“迭代器對象”為“因?qū)ψ兓械募蠈ο蟆碧峁┝艘环N優(yōu)雅的方式摊沉。
對于使用對象的方式來解決聚合對象的的訪問問題,在現(xiàn)在來看痒给,在C++來說已經(jīng)過時了坯钦,現(xiàn)在的STL中主要采用的是泛型編程的思想。
template<typename T>
class Iterator
{
public:
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual T& current() = 0;
};
template<typename T>
class MyCollection{
public:
Iterator<T> GetIterator(){
//...
}
};
template<typename T>
class CollectionIterator : public Iterator<T>{
MyCollection<T> mc;
public:
CollectionIterator(const MyCollection<T> & c): mc(c){ }
void first() override {
}
void next() override {
}
bool isDone() const override{
}
T& current() override{
}
};
void MyAlgorithm()
{
MyCollection<int> mc;
Iterator<int> iter= mc.GetIterator();
for (iter.first(); !iter.isDone(); iter.next()){
cout << iter.current() << endl;
}
}
以上是面向?qū)ο蟮哪J降鞯拇a侈玄。面向?qū)ο蟮奶摵瘮?shù)調(diào)用,是有一定的性能成本吟温,需要間接運算序仙,而在遍歷過程來說,需要大量的重復調(diào)用鲁豪,那么全都是虛函數(shù)調(diào)用潘悼,會使得性能成本倍增律秃。基于泛型編程的模版編程方法治唤,實在編譯時依賴棒动,但是性能相較面向?qū)ο蟮牡鱽碚f會比較低”鎏恚基于運行時的多態(tài)的迭代器船惨,在java、c#這類語言中普遍應用缕陕。
要點總結(jié)
迭代抽象:訪問一個聚合對象的內(nèi)容而無需暴露他的內(nèi)部表示粱锐。
迭代多態(tài):為遍歷不同的集合結(jié)構(gòu)提供一個統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進行操作扛邑。
迭代器健壯性考慮:便利的同時更改迭代器所在的集合結(jié)構(gòu)怜浅,會導致問題。
責任鏈(Chain of Resposibility)
使多個對像都有機會處理請求蔬崩,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系恶座。將這些對像連成一條鏈,并沿著這條鏈傳遞請求沥阳,直到有一個對象處理它為止跨琳。
——《設計模式》GoF
- 在軟件構(gòu)建的過程中,一個請求可能被多個對象處理沪袭,但是每個請求在運行時只能有一個接受者湾宙,如果顯示制定,將必不可少的帶來請求發(fā)送者與接受者的耦合冈绊。
#include <iostream>
#include <string>
using namespace std;
enum class RequestType
{
REQ_HANDLER1,
REQ_HANDLER2,
REQ_HANDLER3
};
//攜帶了請求信息
class Reqest
{
string description;
RequestType reqType;
public:
Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {}
RequestType getReqType() const { return reqType; }
const string& getDescription() const { return description; }
};
//鏈表結(jié)構(gòu)
class ChainHandler{
//自己指向自己侠鳄,形成鏈表
ChainHandler *nextChain;
void sendReqestToNextHandler(const Reqest & req)
{
if (nextChain != nullptr)
nextChain->handle(req);
}
protected:
//判斷請求是否能被處理
virtual bool canHandleRequest(const Reqest & req) = 0;
//處理請求
virtual void processRequest(const Reqest & req) = 0;
public:
ChainHandler() { nextChain = nullptr; }
//完整的處理請求的邏輯
void setNextChain(ChainHandler *next) { nextChain = next; }
void handle(const Reqest & req)
{
if (canHandleRequest(req))
processRequest(req);
else
sendReqestToNextHandler(req);
}
};
class Handler1 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER1;
}
void processRequest(const Reqest & req) override
{
cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler2 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER2;
}
void processRequest(const Reqest & req) override
{
cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler3 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER3;
}
void processRequest(const Reqest & req) override
{
cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
}
};
int main(){
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Reqest req("process task ... ", RequestType::REQ_HANDLER3);
h1.handle(req);
return 0;
}
要點總結(jié)
- Chain of Responsibility模式的應用場合在于“一個請求可能有多個接受者,但是最后真正接受者只有一個”死宣,這時候請求發(fā)送者與接受者的耦合可能出現(xiàn)“變化脆弱”的癥狀伟恶,職責鏈的目的就是將二者解耦,從而更好的應對變化毅该。
- 應用了指責連模式后博秫,對象的指責分派將更具靈活性。我們可以在運行時動態(tài)添加/修改請求的處理指責挡育。
- 如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的缺省機制朴爬。這也是每一個接受者對象的責任即寒,而不是發(fā)出請求的對象的責任。