C++設(shè)計(jì)模式(李建忠)
本文是學(xué)習(xí)筆記,如有侵權(quán),請聯(lián)系刪除。
參考鏈接
Youtube: C++設(shè)計(jì)模式
Gtihub源碼與PPT:https://github.com/ZachL1/Bilibili-plus
豆瓣: 設(shè)計(jì)模式–可復(fù)用面向?qū)ο筌浖幕A(chǔ)
“接口隔離”模式
在組件構(gòu)建過程中,某些接口之間直接的依賴常常會帶來很多問題、甚至根本無法實(shí)現(xiàn)。采用添加一層間接(穩(wěn)定)接口,來隔離本來互相緊密關(guān)聯(lián)的接口是一種常見的解決方案。
典型模式
·Fa?ade
·Proxy
·Adapter
·Mediator
14 外觀模式(Facade)
Motivation
下圖中左邊方案的問題在于組件的客戶和組件中各種復(fù)雜的子系統(tǒng)有了過多的耦合,隨著外部客戶程序和各子系統(tǒng)的演化,這種過多的耦合面臨很多變化的挑戰(zhàn)。
如何簡化外部客戶程序和系統(tǒng)間的交互接口?如何將外部客戶程 序的演化和內(nèi)部子系統(tǒng)的變化之間的依賴相互解耦?
將一個(gè)系統(tǒng)劃分成為若干個(gè)子系統(tǒng)有利于降低系統(tǒng)的復(fù)雜性。一個(gè)常見的設(shè)計(jì)目標(biāo)是使子系統(tǒng)間的通信和相互依賴關(guān)系達(dá)到最小。達(dá)到該目標(biāo)的途徑之一是就是引入一個(gè) 外觀(facade)對象,它為子系統(tǒng)中較一般的設(shè)施提供了一個(gè)單一而簡單的界面。
例如有一個(gè)編程環(huán)境,它允許應(yīng)用程序訪問它的編譯子系統(tǒng)。這個(gè)編譯子系統(tǒng)包含了若干個(gè)類,如 Scanner、Parser、ProgramNode、BytecodeStream 和 ProgramNodeBuilder,用于實(shí)現(xiàn)這一編譯器。有些特殊應(yīng)用程序需要直接訪問這些類,但是大多數(shù)編譯器的用戶并不關(guān)心語法分析和代碼生成這樣的細(xì)節(jié);他們只是希望編譯一些代碼。對這些用戶,編譯子系統(tǒng)中那些功能強(qiáng)大但層次較低的接口只會使他們的任務(wù)復(fù)雜化。
為了提供一個(gè)高層的接口并且對客戶屏蔽這些類,編譯子系統(tǒng)還包括一個(gè) Compiler 類。這個(gè)類定義了一個(gè)編譯器功能的統(tǒng)一接口。 Compiler 類是一個(gè)外觀,它給用戶提供了一個(gè)單一而簡單的編譯子系統(tǒng)接口。它無需完全隱藏實(shí)現(xiàn)編譯功能的那些類,即可將它們結(jié)合在一起。編譯器的外觀可方便大多數(shù)程序員使用,同時(shí)對少數(shù)懂得如何使用底層功能的人,它并不隱藏這些功能,如下圖所示。
Facade模式定義
為子系統(tǒng)中的一組接口提供一個(gè)一致的界面, Facade模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。
Facade結(jié)構(gòu)
參與者
? Facade (Compiler)
— 知道哪些子系統(tǒng)類負(fù)責(zé)處理請求。
— 將客戶的請求代理給適當(dāng)?shù)淖酉到y(tǒng)對象。
? Subsystem classes (Scanner、Parser、ProgramNode 等)
— 實(shí)現(xiàn)子系統(tǒng)的功能。
— 處理由 Facade 對象指派的任務(wù)。
— 沒有 facade 的任何相關(guān)信息;即沒有指向 facade 的指針。
協(xié)作
? 客戶程序通過發(fā)送請求給 Facade 的方式與子系統(tǒng)通訊, Facade 將這些消息轉(zhuǎn)發(fā)給適當(dāng)?shù)淖酉到y(tǒng)對象。盡管是子系統(tǒng)中的有關(guān)對象在做實(shí)際工作,但 Facade 模式本身也必須將它的接口轉(zhuǎn)換成子系統(tǒng)的接口。
? 使用 Facade 的客戶程序不需要直接訪問子系統(tǒng)對象。
要點(diǎn)總結(jié)
從客戶程序的角度來看,F(xiàn)a?ade模式簡化了整個(gè)組件系統(tǒng)的接口, 對于組件內(nèi)部與外部客戶程序來說,達(dá)到了一種“解耦”的效 果——內(nèi)部子系統(tǒng)的任何變化不會影響到Fa?ade接口的變化。
Fa?ade設(shè)計(jì)模式更注重從架構(gòu)的層次去看整個(gè)系統(tǒng),而不是單個(gè)類的層次。Fa?ade很多時(shí)候更是一種架構(gòu)設(shè)計(jì)模式。
Fa?ade設(shè)計(jì)模式并非一個(gè)集裝箱,可以任意地放進(jìn)任何多個(gè)對象。Facade模式中組件的內(nèi)部應(yīng)該是“相互耦合關(guān)系比較大的一系列 組件”,而不是一個(gè)簡單的功能集合。
15 代理模式(Proxy)
Motivation
在面向?qū)ο笙到y(tǒng)中,有些對象由于某種原因(比如對象創(chuàng)建的開銷很大,或者某些操作需要安全控制,或者需要進(jìn)程外的訪問等),直接訪問會給使用者、或者系統(tǒng)結(jié)構(gòu)帶來很多麻煩。
如何在不失去透明操作對象的同時(shí)來管理/控制這些對象特有的復(fù) 雜性?增加一層間接層是軟件開發(fā)中常見的解決方式。
代碼示例
class ISubject{
public:
virtual void process();
};
//Proxy的設(shè)計(jì)
class SubjectProxy: public ISubject{
public:
virtual void process(){
//對RealSubject的一種間接訪問
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new SubjectProxy();
}
void DoTask(){
//...
subject->process();
//....
}
};
Proxy模式定義
為其他對象提供一種代理以控制對這個(gè)對象的訪問。
Proxy結(jié)構(gòu)
參與者
? Proxy
— 保存一個(gè)引用使得代理可以訪問實(shí)體。若 RealSubject 和 Subject 的接口相同, Proxy 會引用 Subject。
— 提供一個(gè)與 Subject 的接口相同的接口,這樣代理就可以用來替代實(shí)體。
— 控制對實(shí)體的存取,并可能負(fù)責(zé)創(chuàng)建和刪除它。
? Subject
— 定義 RealSubject 和 Proxy 的共用接口,這樣就在任何使用 RealSubject 的地方都可以使用Proxy。
? RealSubject
— 定義 Proxy 所代表的實(shí)體。
chatGPT給出的proxy例子
以下是一個(gè)簡單的 C++ 代理模式的例子,其中實(shí)現(xiàn)了一個(gè)圖片加載的代理。
#include <iostream>
#include <string>
// Subject 接口
class Image {
public:
virtual void display() const = 0;
};
// RealSubject 類
class RealImage : public Image {
private:
std::string filename;
public:
RealImage(const std::string& filename) : filename(filename) {
loadImage();
}
void loadImage() {
std::cout << "Loading image: " << filename << std::endl;
}
void display() const override {
std::cout << "Displaying image: " << filename << std::endl;
}
};
// Proxy 類
class ImageProxy : public Image {
private:
RealImage* realImage;
std::string filename;
public:
ImageProxy(const std::string& filename) : realImage(nullptr), filename(filename) {}
void display() const override {
if (realImage == nullptr) {
realImage = new RealImage(filename);
}
realImage->display();
}
};
// Client 代碼
int main() {
// 使用代理加載圖片,實(shí)際圖片加載在需要顯示時(shí)進(jìn)行
Image* image = new ImageProxy("sample.jpg");
// 第一次顯示圖片,會觸發(fā)加載
image->display();
// 第二次顯示圖片,直接使用已加載的圖片
image->display();
return 0;
}
在這個(gè)例子中,Image
是代理模式的 Subject
接口,RealImage
是 RealSubject
類,負(fù)責(zé)實(shí)際加載和顯示圖片。ImageProxy
是代理類,用于延遲加載和控制對 RealImage
對象的訪問??蛻舳送ㄟ^代理類使用圖片,代理類在需要時(shí)創(chuàng)建或使用實(shí)際圖片對象。
分布式系統(tǒng)中代理模式的使用
在分布式系統(tǒng)中,代理模式被廣泛應(yīng)用以解決各種問題。以下是一些在分布式系統(tǒng)中常見的代理模式應(yīng)用:
-
遠(yuǎn)程代理(Remote Proxy): 遠(yuǎn)程代理用于在不同的地址空間中代表對象。在分布式系統(tǒng)中,對象可能存在于不同的節(jié)點(diǎn)或服務(wù)器上,遠(yuǎn)程代理允許客戶端通過代理訪問遠(yuǎn)程對象,就像訪問本地對象一樣。
-
虛擬代理(Virtual Proxy): 虛擬代理用于延遲對象的創(chuàng)建和初始化,特別是在對象很大或需要耗費(fèi)大量資源的情況下。在分布式系統(tǒng)中,虛擬代理可以用于延遲加載遠(yuǎn)程對象,避免一開始就加載所有的遠(yuǎn)程資源。
-
安全代理(Protection Proxy): 安全代理用于控制對對象的訪問,包括對某些操作的權(quán)限驗(yàn)證。在分布式系統(tǒng)中,安全代理可以用于對遠(yuǎn)程服務(wù)的訪問進(jìn)行身份驗(yàn)證和權(quán)限控制。
-
緩存代理(Caching Proxy): 緩存代理用于緩存一些開銷較大的操作的結(jié)果,以提高性能。在分布式系統(tǒng)中,可以使用緩存代理來緩存遠(yuǎn)程服務(wù)的響應(yīng),減少不必要的網(wǎng)絡(luò)開銷。
-
負(fù)載均衡代理(Load Balancing Proxy): 負(fù)載均衡代理用于分發(fā)請求到多個(gè)服務(wù)器,以平衡系統(tǒng)的負(fù)載。在分布式系統(tǒng)中,負(fù)載均衡代理可以用于在多個(gè)節(jié)點(diǎn)之間分發(fā)請求,提高系統(tǒng)的整體性能和可擴(kuò)展性。
-
日志記錄代理(Logging Proxy): 日志記錄代理用于記錄對象的操作,以便進(jìn)行調(diào)試、分析或?qū)徲?jì)。在分布式系統(tǒng)中,日志記錄代理可以用于記錄遠(yuǎn)程服務(wù)的請求和響應(yīng),幫助排查問題和監(jiān)控系統(tǒng)狀態(tài)。
這些代理模式的應(yīng)用有助于提高分布式系統(tǒng)的靈活性、安全性、性能和可維護(hù)性。代理模式在分布式系統(tǒng)中的使用可以根據(jù)具體的需求和架構(gòu)進(jìn)行靈活選擇和組合。
要點(diǎn)總結(jié)
“增加一層間接層”是軟件系統(tǒng)中對許多復(fù)雜問題的一種常見解 決方法。在面向?qū)ο笙到y(tǒng)中,直接使用某些對象會帶來很多問題, 作為間接層的proxy對象便是解決這一問題的常用手段。
具體proxy設(shè)計(jì)模式的實(shí)現(xiàn)方法、實(shí)現(xiàn)粒度都相差很大,有些可能對單個(gè)對象做細(xì)粒度的控制,如copy-on-write技術(shù),有些可能對組件模塊提供抽象代理層,在架構(gòu)層次對對象做proxy。
Proxy并不一定要求保持接口完整的一致性,只要能夠?qū)崿F(xiàn)間接控制,有時(shí)候損及一些透明性是可以接受的。
16 適配器(Adapter)
Motivation
在軟件系統(tǒng)中,由于應(yīng)用環(huán)境的變化,常常需要將“一些現(xiàn)存的對象”放在新的環(huán)境中應(yīng)用,但是新環(huán)境要求的接口是這些現(xiàn)存對象所不滿足的。
如何應(yīng)對這種“遷移的變化”?如何既能利用現(xiàn)有對象的良好實(shí)現(xiàn),同時(shí)又能滿足新的應(yīng)用環(huán)境所要求的接口?
代碼示例
//目標(biāo)接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遺留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遺留類型
class OldClass: public IAdaptee{
//....
};
//對象適配器
class Adapter: public ITarget{ //繼承
protected:
IAdaptee* pAdaptee;//組合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//類適配器
class Adapter: public ITarget,
protected OldClass{ //多繼承
}
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
class stack{
deque container;
};
class queue{
deque container;
};
適配器模式定義
將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
適配器模式結(jié)構(gòu)
參與者
? Target
— 定義 Client 使用的與特定領(lǐng)域相關(guān)的接口。
? Client
— 與符合 Target 接口的對象協(xié)同。
? Adaptee
— 定義一個(gè)已經(jīng)存在的接口,這個(gè)接口需要適配。以前的、遺留的接口。
? Adapter
— 對 Adaptee 的接口與 Target 接口進(jìn)行適配。
協(xié)作
? Client 在 Adapter 實(shí)例上調(diào)用一些操作。接著適配器調(diào)用 Adaptee 的操作實(shí)現(xiàn)這個(gè)請求。
要點(diǎn)總結(jié)
Adapter模式主要應(yīng)用于“希望復(fù)用一些現(xiàn)存的類,但是接口又與復(fù)用環(huán)境要求不一致的情況”,在遺留代碼復(fù)用、類庫遷移等方面非常有用。
GoF23定義了兩種Adapter模式的實(shí)現(xiàn)結(jié)構(gòu):對象適配器和類適配器。但類適配器采用多繼承”的實(shí)現(xiàn)方式,一般不推薦使用。對象適配器采用”對象組合”的方式,更符合松耦合精神。
Adapter模式可以實(shí)現(xiàn)的非常靈活,不必拘泥于Gof23中定義的兩種結(jié)構(gòu)。例如,完全可以將Adapter模式中的“現(xiàn)存對象”作為新的接口方法參數(shù),來達(dá)到適配的目的。
chatGPT給出的adapter例子
下面是一個(gè)簡單的C++ Adapter 模式的例子,假設(shè)有一個(gè)舊的類 OldClass
,其接口不符合新的需求,然后通過適配器 Adapter
將其適配為符合新需求的接口:
#include <iostream>
// 舊的類,接口不符合新需求
class OldClass {
public:
void legacyMethod() {
std::cout << "Legacy method of OldClass" << std::endl;
}
};
// 新的接口,符合新需求
class NewInterface {
public:
virtual void newMethod() = 0;
virtual ~NewInterface() {}
};
// 適配器,將OldClass適配為NewInterface
class Adapter : public NewInterface {
private:
OldClass oldInstance;
public:
void newMethod() override {
// 在這里調(diào)用OldClass的方法,以適應(yīng)新的接口
oldInstance.legacyMethod();
}
};
// 客戶端代碼,使用新的接口
void clientCode(NewInterface* newObject) {
newObject->newMethod();
}
int main() {
// 使用適配器將OldClass適配為NewInterface
Adapter adapter;
// 客戶端代碼使用新的接口
clientCode(&adapter);
return 0;
}
在這個(gè)例子中,OldClass
是一個(gè)舊的類,具有 legacyMethod
方法,但其接口不符合 NewInterface
的新需求。通過創(chuàng)建一個(gè)適配器類 Adapter
,它繼承了 NewInterface
并持有一個(gè) OldClass
的實(shí)例,將 legacyMethod
適配為 newMethod
。最后,在客戶端代碼中,我們可以使用 NewInterface
的接口,而實(shí)際上調(diào)用了 OldClass
的方法。這就是 Adapter 模式的作用。
17 中介者(Mediator)
Motivation
在軟件構(gòu)建過程中,經(jīng)常會出現(xiàn)多個(gè)對象互相關(guān)聯(lián)交互的情況,對象之間常常會維持一種復(fù)雜的引用關(guān)系,如果遇到一些需求的更改,這種直接的引用關(guān)系將面臨不斷的變化。
在這種情況下,我們可使用一個(gè)“中介對象”來管理對象間的關(guān)聯(lián)關(guān)系,避免相互交互的對象之間的緊耦合引用關(guān)系,從而更好地抵御變化。
Mediator定義
用一個(gè)中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。
Mediator結(jié)構(gòu)
參與者
? Mediator (中介者)
— 中介者定義一個(gè)接口用于與各同事(Colleague)對象通信。
? Concrete Mediator (具體中介者)
— 具體中介者通過協(xié)調(diào)各同事對象實(shí)現(xiàn)協(xié)作行為。
— 了解并維護(hù)它的各個(gè)同事。
? Colleague class (同事類)
— 每一個(gè)同事類都知道它的中介者對象。
— 每一個(gè)同事對象在需與其他的同事通信的時(shí)候,與它的中介者通信。
一個(gè)可能的對象結(jié)構(gòu):
代碼示例
中介者模式是一種行為型設(shè)計(jì)模式,其主要目的是減少對象之間的直接通信,而是通過一個(gè)中介者對象進(jìn)行協(xié)調(diào)。這可以降低對象之間的耦合度,使系統(tǒng)更易于維護(hù)和擴(kuò)展。
在中介者模式中,有以下幾個(gè)主要參與者:
-
Mediator (中介者): 定義一個(gè)接口用于與各同事(Colleague)對象通信。中介者負(fù)責(zé)協(xié)調(diào)各同事之間的交互,通過中介者進(jìn)行通信而不是直接相互引用。
-
Concrete Mediator (具體中介者): 實(shí)現(xiàn)中介者接口,通過協(xié)調(diào)各同事對象來實(shí)現(xiàn)協(xié)作行為。具體中介者了解并維護(hù)它的各個(gè)同事。
-
Colleague class (同事類): 每一個(gè)同事類都知道它的中介者對象,而不知道其他同事的存在。每一個(gè)同事對象在需要與其他同事通信的時(shí)候,通過它的中介者來實(shí)現(xiàn)通信。
中介者模式的典型應(yīng)用場景是當(dāng)一個(gè)系統(tǒng)中對象之間的交互復(fù)雜且對象之間相互依賴性較高時(shí)。通過引入中介者,可以將系統(tǒng)從多對多的關(guān)系簡化為多對一的關(guān)系,降低了系統(tǒng)的復(fù)雜性。
以下是一個(gè)簡單的中介者模式的示例,假設(shè)有一個(gè)聊天室系統(tǒng):
#include <iostream>
#include <string>
class Mediator;
// 同事類
class Colleague {
protected:
Mediator* mediator;
public:
Colleague(Mediator* mediator) : mediator(mediator) {}
virtual void send(const std::string& message) = 0;
virtual void receive(const std::string& message) = 0;
};
// 具體同事類
class ConcreteColleague : public Colleague {
public:
ConcreteColleague(Mediator* mediator) : Colleague(mediator) {}
void send(const std::string& message) override {
std::cout << "Sending message: " << message << std::endl;
mediator->mediate(this, message);
}
void receive(const std::string& message) override {
std::cout << "Received message: " << message << std::endl;
}
};
// 中介者接口
class Mediator {
public:
virtual void mediate(Colleague* sender, const std::string& message) = 0;
};
// 具體中介者
class ConcreteMediator : public Mediator {
private:
Colleague* colleague1;
Colleague* colleague2;
public:
void setColleague1(Colleague* colleague) {
colleague1 = colleague;
}
void setColleague2(Colleague* colleague) {
colleague2 = colleague;
}
void mediate(Colleague* sender, const std::string& message) override {
if (sender == colleague1) {
colleague2->receive(message);
} else if (sender == colleague2) {
colleague1->receive(message);
}
}
};
int main() {
ConcreteMediator mediator;
ConcreteColleague colleague1(&mediator);
ConcreteColleague colleague2(&mediator);
mediator.setColleague1(&colleague1);
mediator.setColleague2(&colleague2);
colleague1.send("Hello from Colleague 1");
colleague2.send("Hi from Colleague 2");
return 0;
}
在這個(gè)例子中,Colleague
表示同事類的抽象,ConcreteColleague
是具體的同事類,Mediator
是中介者的抽象,而 ConcreteMediator
是具體的中介者。同事對象通過中介者來進(jìn)行通信。
要點(diǎn)總結(jié)
將多個(gè)對象間復(fù)雜的關(guān)聯(lián)關(guān)系解耦,Mediator模式將多個(gè)對象間的控制邏輯進(jìn)行集中管理,變“多個(gè)對象互相關(guān)聯(lián)”為“多個(gè)對象和一個(gè)中介者關(guān)聯(lián)”,簡化了系統(tǒng)的維護(hù),抵御了可能的變化。
隨著控制邏輯的復(fù)雜化,Mediator具體對象的實(shí)現(xiàn)可能相當(dāng)復(fù)雜。這時(shí)候可以對Mediator對象進(jìn)行分解處理。
Fa?ade模式是解耦系統(tǒng)間(單向)的對象關(guān)聯(lián)關(guān)系;Mediator模式是解耦系統(tǒng)內(nèi)各個(gè)對象之間(雙向)的關(guān)聯(lián)關(guān)系。
Mediator和Facade的區(qū)別
中介者模式和外觀模式(Facade 模式)是兩種不同的設(shè)計(jì)模式,它們解決了不同的問題,并在實(shí)現(xiàn)上有一些區(qū)別。
-
中介者模式(Mediator Pattern):
- 目的: 中介者模式的主要目的是通過減少對象之間的直接通信,來解耦對象之間的關(guān)系。中介者模式通過引入一個(gè)中介者對象,將對象之間的通信集中到中介者對象,從而降低對象之間的耦合度。
- 實(shí)現(xiàn): 中介者模式通常包括中介者接口和具體中介者類,同時(shí)每個(gè)對象都持有中介者的引用。對象通過中介者進(jìn)行通信,而不直接與其他對象通信。
- 例子: 聊天室是一個(gè)經(jīng)典的中介者模式的例子,其中聊天室充當(dāng)中介者,聊天室成員通過聊天室進(jìn)行消息的發(fā)送和接收。
-
外觀模式(Facade Pattern):
- 目的: 外觀模式的主要目的是提供一個(gè)簡化的接口,用于訪問系統(tǒng)的復(fù)雜子系統(tǒng)。外觀模式通過引入一個(gè)外觀類,將客戶端與子系統(tǒng)的復(fù)雜性隔離,使客戶端只需與外觀類進(jìn)行交互,而不需要直接了解子系統(tǒng)的細(xì)節(jié)。
- 實(shí)現(xiàn): 外觀模式包括一個(gè)外觀類,該類封裝了子系統(tǒng)的接口,并為客戶端提供一個(gè)簡化的接口??蛻舳酥恍柰ㄟ^外觀類來與系統(tǒng)交互,而不需要了解系統(tǒng)內(nèi)部的復(fù)雜結(jié)構(gòu)。
- 例子: 電腦啟動(dòng)過程中的 BIOS、操作系統(tǒng)加載、應(yīng)用程序啟動(dòng)等過程可以使用外觀模式來簡化客戶端與這些復(fù)雜子系統(tǒng)的交互。
區(qū)別總結(jié):
- 目的不同: 中介者模式的主要目的是解耦對象之間的關(guān)系,降低耦合度;外觀模式的主要目的是提供一個(gè)簡化的接口,隔離客戶端與子系統(tǒng)的復(fù)雜性。
- 涉及對象數(shù)量: 中介者模式通常涉及多個(gè)對象之間的通信,而外觀模式通常涉及對多個(gè)子系統(tǒng)的封裝。
- 關(guān)注點(diǎn): 中介者模式關(guān)注對象之間的通信和解耦,而外觀模式關(guān)注對復(fù)雜系統(tǒng)的簡化接口。
在實(shí)際應(yīng)用中,選擇使用中介者模式還是外觀模式取決于問題的性質(zhì)和需求。
“狀態(tài)變化”模式
在組件構(gòu)建過程中,某些對象的狀態(tài)經(jīng)常面臨變化,如何對這些變化進(jìn)行有效的管理?同時(shí)又維持高層模塊的穩(wěn)定? “狀態(tài)變化” 模式為這一問題提供了一種解決方案。
典型模式
·State
·Memento
18 狀態(tài)模式(State)
Motivation
在軟件構(gòu)建過程中,某些對象的狀態(tài)如果改變,其行為也會隨之而發(fā)生變化,比如文檔處于只讀狀態(tài),其支持的行為和讀寫狀態(tài)支持的行為就可能完全不同。
如何在運(yùn)行時(shí)根據(jù)對象的狀態(tài)來透明地更改對象的行為?而不會為對象操作和狀態(tài)轉(zhuǎn)化之間引入緊耦合?
考慮一個(gè)表示網(wǎng)絡(luò)連接的類TCPConnection。一個(gè)TCPConnection對象的狀態(tài)處于若干不同狀態(tài)之一:連接已建立(Established)、正在監(jiān)聽(Listening)、連接已關(guān)閉(Closed)。當(dāng)一個(gè)TCPConnection對象收到其他對象的請求時(shí),它根據(jù)自身的當(dāng)前狀態(tài)作出不同的反應(yīng)。例如,一個(gè)Open請求的結(jié)果依賴于該連接是處于連接已關(guān)閉狀態(tài)還是連接已建立狀態(tài)。State模式描述了TCPConnection如何在每一種狀態(tài)下表現(xiàn)出不同的行為。
這一模式的關(guān)鍵思想是引入了一個(gè)稱為TCPState的抽象類來表示網(wǎng)絡(luò)的連接狀態(tài)。TCPState類為各表示不同的操作狀態(tài)的子類聲明了一個(gè)公共接口。TCPState的子類實(shí)現(xiàn)與特定狀態(tài)相關(guān)的行為。例如,TCPEstablished和TCPClosed類分別實(shí)現(xiàn)了特定于TCPConnection的連接已建立狀態(tài)和連接已關(guān)閉狀態(tài)的行為。
TCPConnection類維護(hù)一個(gè)表示TCP連接當(dāng)前狀態(tài)的狀態(tài)對象(一個(gè)TCPState子類的實(shí)例)。TCPConnection類將所有與狀態(tài)相關(guān)的請求委托給這個(gè)狀態(tài)對象。TCPConnection使用它的TCPState子類實(shí)例來執(zhí)行特定于連接狀態(tài)的操作。
一旦連接狀態(tài)改變,TCPConnection對象就會改變它所使用的狀態(tài)對象。例如當(dāng)連接從已建立狀態(tài)轉(zhuǎn)為已關(guān)閉狀態(tài)時(shí),TCPConnection會用一個(gè)TCPClosed的實(shí)例來代替原來的TCPEstablished的實(shí)例。
狀態(tài)模式定義
允許一個(gè)對象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。對象看起來似乎修改了它的類。
狀態(tài)模式結(jié)構(gòu)
參與者
? Context (環(huán)境,如 TCPConnection)
— 定義客戶感興趣的接口。
— 維護(hù)一個(gè)ConcreteState子類的實(shí)例,這個(gè)實(shí)例定義當(dāng)前狀態(tài)。
? State (狀態(tài),如 TCPState)
— 定義一個(gè)接口以封裝與Context 的一個(gè)特定狀態(tài)相關(guān)的行為。
? ConcreteState subclasses (具體狀態(tài)子類,如 TCPEstablished, TCPListen, TCPClosed)
— 每一子類實(shí)現(xiàn)一個(gè)與Context的一個(gè)狀態(tài)相關(guān)的行為。
代碼舉例
假設(shè)我們有一個(gè)簡單的文檔編輯器,其中文檔可以處于三種狀態(tài):草稿(Draft)、審核中(UnderReview)和已發(fā)布(Published)。我們可以使用 State 模式來管理這些不同的文檔狀態(tài)。
首先,定義文檔狀態(tài)的抽象基類 DocumentState
:
#include <iostream>
class Document;
class DocumentState {
public:
virtual void Edit(Document* document) = 0;
virtual void Review(Document* document) = 0;
virtual void Publish(Document* document) = 0;
};
接下來,實(shí)現(xiàn)具體的文檔狀態(tài)類:
class DraftState : public DocumentState {
public:
void Edit(Document* document) override;
void Review(Document* document) override;
void Publish(Document* document) override;
};
class UnderReviewState : public DocumentState {
public:
void Edit(Document* document) override;
void Review(Document* document) override;
void Publish(Document* document) override;
};
class PublishedState : public DocumentState {
public:
void Edit(Document* document) override;
void Review(Document* document) override;
void Publish(Document* document) override;
};
然后,定義文檔類 Document
,該類維護(hù)當(dāng)前文檔的狀態(tài),并委托狀態(tài)對象來處理文檔的編輯、審核和發(fā)布操作:
class Document {
private:
DocumentState* currentState;
public:
Document() : currentState(new DraftState()) {}
void ChangeState(DocumentState* newState) {
delete currentState;
currentState = newState;
}
void Edit() {
currentState->Edit(this);
}
void Review() {
currentState->Review(this);
}
void Publish() {
currentState->Publish(this);
}
~Document() {
delete currentState;
}
};
最后,實(shí)現(xiàn)具體的文檔狀態(tài)類的方法:
// Implementation of DraftState methods
void DraftState::Edit(Document* document) {
std::cout << "Editing the document." << std::endl;
}
void DraftState::Review(Document* document) {
std::cout << "Reviewing is not applicable in Draft state." << std::endl;
}
void DraftState::Publish(Document* document) {
std::cout << "Publishing is not applicable in Draft state." << std::endl;
}
// Implementation of UnderReviewState methods
void UnderReviewState::Edit(Document* document) {
std::cout << "Editing is not allowed during review." << std::endl;
}
void UnderReviewState::Review(Document* document) {
std::cout << "Reviewing the document." << std::endl;
}
void UnderReviewState::Publish(Document* document) {
std::cout << "Publishing is not allowed during review." << std::endl;
}
// Implementation of PublishedState methods
void PublishedState::Edit(Document* document) {
std::cout << "Editing is not allowed for published documents." << std::endl;
}
void PublishedState::Review(Document* document) {
std::cout << "Reviewing is not allowed for published documents." << std::endl;
}
void PublishedState::Publish(Document* document) {
std::cout << "The document is already published." << std::endl;
}
在這個(gè)例子中,文檔的狀態(tài)包括草稿、審核中和已發(fā)布,而 Document
類委托給具體的狀態(tài)對象來處理不同狀態(tài)下的操作。這樣,文檔類的行為可以根據(jù)其當(dāng)前狀態(tài)的變化而動(dòng)態(tài)改變。
下面是一個(gè)簡單的 main
函數(shù),演示了如何使用上述定義的文檔類和狀態(tài)類:
int main() {
Document document;
// Initial state is Draft
document.Edit(); // Output: Editing the document.
document.Review(); // Output: Reviewing is not applicable in Draft state.
document.Publish(); // Output: Publishing is not applicable in Draft state.
// Change state to UnderReview
document.ChangeState(new UnderReviewState());
document.Edit(); // Output: Editing is not allowed during review.
document.Review(); // Output: Reviewing the document.
document.Publish(); // Output: Publishing is not allowed during review.
// Change state to Published
document.ChangeState(new PublishedState());
document.Edit(); // Output: Editing is not allowed for published documents.
document.Review(); // Output: Reviewing is not allowed for published documents.
document.Publish(); // Output: The document is already published.
return 0;
}
在這個(gè) main
函數(shù)中,我們創(chuàng)建了一個(gè)文檔對象,并在不同狀態(tài)下調(diào)用了 Edit
、Review
和 Publish
方法。通過改變文檔的狀態(tài),我們可以看到文檔對象的行為隨之改變,符合狀態(tài)模式的設(shè)計(jì)思想。
要點(diǎn)總結(jié)
State模式將所有與一個(gè)特定狀態(tài)相關(guān)的行為都放入一個(gè)State的子類對象中,在對象狀態(tài)切換時(shí),切換相應(yīng)的對象;但同時(shí)維持State 的接口,這樣實(shí)現(xiàn)了具體操作與狀態(tài)轉(zhuǎn)換之間的解耦。
為不同的狀態(tài)引入不同的對象使得狀態(tài)轉(zhuǎn)換變得更加明確,而且可以保證不會出現(xiàn)狀態(tài)不一致的情況,因?yàn)檗D(zhuǎn)換是原子性的——即要么徹底轉(zhuǎn)換過來,要么不轉(zhuǎn)換。
如果State對象沒有實(shí)例變量,那么各個(gè)上下文可以共享同一個(gè)State對象,從而節(jié)省對象開銷。
以下是狀態(tài)模式和策略模式之間的區(qū)別:
-
關(guān)注點(diǎn):
- 狀態(tài)模式: 側(cè)重于允許對象在其內(nèi)部狀態(tài)發(fā)生變化時(shí)更改其行為。它關(guān)注于表示對象的狀態(tài)以及狀態(tài)之間的轉(zhuǎn)換。
- 策略模式: 側(cè)重于定義一組算法,封裝每個(gè)算法,并使它們可以互換。它允許客戶端選擇適當(dāng)?shù)乃惴ǘ鵁o需更改客戶端代碼。
-
狀態(tài)/策略轉(zhuǎn)換的責(zé)任:
- 狀態(tài)模式: 狀態(tài)之間的轉(zhuǎn)換通常由上下文對象內(nèi)部控制。上下文對象通過更改其內(nèi)部狀態(tài)來改變其行為。
- 策略模式: 特定策略(算法)的選擇通常由客戶端或外部實(shí)體控制??蛻舳藳Q定使用哪種策略,并可以在策略之間動(dòng)態(tài)切換。
備忘錄模式(Memento)
Motivation
在軟件構(gòu)建過程中,某些對象的狀態(tài)在轉(zhuǎn)換過程中,可能由于某種需要,要求程序能夠回溯到對象之前處于某個(gè)點(diǎn)時(shí)的狀態(tài)。如果使用一些公有接口來讓其他對象得到對象的狀態(tài),便會暴露對象的細(xì)節(jié)實(shí)現(xiàn)。
如何實(shí)現(xiàn)對象狀態(tài)的良好保存與恢復(fù)?但同時(shí)又不會因此而破壞 對象本身的封裝性。
代碼舉例
#include <iostream>
#include <string>
// Memento(備忘錄)類:用于存儲 Originator 內(nèi)部狀態(tài)的快照
class Memento {
private:
std::string state; // 內(nèi)部狀態(tài)
public:
Memento(const std::string& s) : state(s) {} // 構(gòu)造函數(shù),初始化內(nèi)部狀態(tài)
std::string getState() const { return state; } // 獲取內(nèi)部狀態(tài)
void setState(const std::string& s) { state = s; } // 設(shè)置內(nèi)部狀態(tài)
};
// Originator(原發(fā)器)類:擁有需要存儲的內(nèi)部狀態(tài),并能夠創(chuàng)建和恢復(fù)備忘錄
class Originator {
private:
std::string state; // 內(nèi)部狀態(tài)
public:
Originator() {} // 默認(rèn)構(gòu)造函數(shù)
Memento createMomento() {
Memento m(state); // 創(chuàng)建備忘錄,保存當(dāng)前內(nèi)部狀態(tài)
return m;
}
void setMomento(const Memento& m) {
state = m.getState(); // 從備忘錄中恢復(fù)內(nèi)部狀態(tài)
}
};
int main() {
Originator orginator;
// 捕獲對象狀態(tài),存儲到備忘錄
Memento memento = orginator.createMomento();
// ... 改變 orginator 狀態(tài)
// 從備忘錄中恢復(fù)
orginator.setMomento(memento);
return 0;
}
Momento定義
在不破壞封裝性的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài)。這樣以后就可將該對象恢復(fù)到原先保存的狀態(tài)。
Momento結(jié)構(gòu)
參與者
? Memento (備忘錄)
— 備忘錄存儲原發(fā)器對象的內(nèi)部狀態(tài)。原發(fā)器根據(jù)需要決定備忘錄存儲原發(fā)器的哪些內(nèi)部狀態(tài)。
— 防止原發(fā)器以外的其他對象訪問備忘錄。備忘錄實(shí)際上有兩個(gè)接口,管理者(caretaker)只能看到備忘錄的窄接口—它只能將備忘錄傳遞給其他對象。相反,原
發(fā)器能夠看到一個(gè)寬接口,允許它訪問返回到先前狀態(tài)所需的所有數(shù)據(jù)。理想的情況是只允許生成本備忘錄的那個(gè)原發(fā)器訪問本備忘錄的內(nèi)部狀態(tài)。
? Originator (原發(fā)器)
— 原發(fā)器創(chuàng)建一個(gè)備忘錄,用以記錄當(dāng)前時(shí)刻它的內(nèi)部狀態(tài)。
— 使用備忘錄恢復(fù)內(nèi)部狀態(tài)。
? Caretaker (負(fù)責(zé)人)
— 負(fù)責(zé)保存好備忘錄。
— 不能對備忘錄的內(nèi)容進(jìn)行操作或檢查。
要點(diǎn)總結(jié)
備忘錄(Memento)存儲原發(fā)器(Originator)對象的內(nèi)部狀態(tài),在需要時(shí)恢復(fù)原發(fā)器狀態(tài)。
Memento模式的核心是信息隱藏,即Originator需要向外接隱藏信息,保持其封裝性。但同時(shí)又需要將狀態(tài)保持到外界(Memento )。
由于現(xiàn)代語言運(yùn)行時(shí)(如C#、Java等)都具有相當(dāng)?shù)膶ο笮蛄谢С?,因此往往采用效率較高、又較容易正確實(shí)現(xiàn)的序列化方案來實(shí)現(xiàn)Memento模式。
chatGPT給出的一個(gè)備忘錄模式的例子
Momento 模式旨在捕獲一個(gè)對象的內(nèi)部狀態(tài),以便稍后可以將其恢復(fù)到此狀態(tài)。以下是一個(gè)簡單的 C++ 示例,演示 Momento 模式的基本概念:
#include <iostream>
#include <string>
#include <vector>
// Momento:存儲原發(fā)器的內(nèi)部狀態(tài)
class Memento {
public:
Memento(const std::string& state) : state_(state) {}
std::string GetState() const {
return state_;
}
private:
std::string state_;
};
// Originator:創(chuàng)建并恢復(fù)到 Momento 的狀態(tài)
class Originator {
public:
Originator() : state_("") {}
void SetState(const std::string& state) {
state_ = state;
std::cout << "Set state to: " << state << std::endl;
}
Memento CreateMemento() {
return Memento(state_);
}
void RestoreMemento(const Memento& memento) {
state_ = memento.GetState();
std::cout << "Restored to state: " << state_ << std::endl;
}
private:
std::string state_;
};
// Caretaker:負(fù)責(zé)保存和恢復(fù) Momento
class Caretaker {
public:
void AddMemento(const Memento& memento) {
momentos_.push_back(memento);
}
Memento GetMemento(int index) const {
return momentos_[index];
}
private:
std::vector<Memento> momentos_;
};
int main() {
// 創(chuàng)建 Originator
Originator originator;
// 創(chuàng)建 Caretaker
Caretaker caretaker;
// 設(shè)置狀態(tài)并保存 Momento
originator.SetState("State1");
caretaker.AddMemento(originator.CreateMemento());
// 設(shè)置新狀態(tài)并保存 Momento
originator.SetState("State2");
caretaker.AddMemento(originator.CreateMemento());
// 恢復(fù)到先前狀態(tài)
originator.RestoreMemento(caretaker.GetMemento(0));
return 0;
}
在此示例中,Originator
表示擁有內(nèi)部狀態(tài)的對象,Memento
表示保存狀態(tài)的 Momento,而 Caretaker
負(fù)責(zé)管理 Momento。在主函數(shù)中,我們創(chuàng)建了 Originator
和 Caretaker
,并演示了如何設(shè)置狀態(tài)、創(chuàng)建 Momento、保存 Momento、設(shè)置新狀態(tài)以及通過 Momento 恢復(fù)到先前狀態(tài)。文章來源:http://www.zghlxwxcb.cn/news/detail-801821.html
后記
截至2024年1月17日20點(diǎn)27分,完成Facade, Proxy, Adapter, Mediator, State, Memento模式的學(xué)習(xí)。后面還有6個(gè)模式需要跟進(jìn)。文章來源地址http://www.zghlxwxcb.cn/news/detail-801821.html
到了這里,關(guān)于C++設(shè)計(jì)模式(李建忠)筆記3的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!