設計模式之單例、工廠、釋出訂閱者模式設計模式

小魔童哪吒發表於2021-03-07
[TOC]

單例模式

​ 保證一個類僅有一個例項,並提供一個該例項的全域性訪問點

​ 在軟體系統中,經常有這樣一些特殊的類,必須保證他們 在系統中只存在一個例項,才能確保它們的邏輯正確性, 以及良好的效率

應用場景:

DBPool 、讀取配置檔案

單例模式分類:

  • 1、懶漢式 – 需要使用單例的時候,才進行初始化
  • 2、餓漢式 – 未呼叫單例的時候,已經進行初始化

寫一個單例模式的demo

#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

//設計執行緒的個數
#define PTHREAD_NUM  20
//懶漢式 餓漢式 單例模式的選型
#define SINGELTON_SELECTOR 0

//單例模式

#if SINGELTON_SELECTOR

//懶漢式  -- 呼叫的時候才初始化
class Singleton{

private:
    Singleton(){
        cout<<"Singleton construct  1111\n";
    }
    ~Singleton(){
        cout<<"Singleton destruct   1111\n";
    }

    //禁止拷貝構造
    Singleton(const Singleton &si) = delete;
    //禁止等號賦值
    Singleton & operator=(const Singleton &si) = delete;

public:
    static Singleton * getInstance(){
        static Singleton m_singleton;
        return &m_singleton;
    }
};

#else

//餓漢式 -- 呼叫之前就已經初始化好,呼叫的時候直接返回地址
class Singleton{

private:
    Singleton(){
        cout<<"Singleton construct   2222\n";
    }
    ~Singleton(){
        cout<<"Singleton destruct    2222\n";
    }

    //禁止拷貝構造
    Singleton(const Singleton &si) = delete;
    //禁止等號賦值
    Singleton & operator=(const Singleton &si) = delete;
    static Singleton m_singleton;

public:
    static Singleton * getInstance(){
        return &m_singleton;
    }
};

Singleton Singleton::m_singleton;


#endif 

//定義一個互斥鎖,保證只有一個執行緒在列印 單例變數的地址
static mutex m;
void print_address()
{
    Singleton* singleton = Singleton::getInstance();

    m.lock();
    cout<<singleton<<endl;
    m.unlock();
}
//測試單例模式
void test_singleton()
{

    thread threads[PTHREAD_NUM];
    for(auto &t : threads)
        t = thread(print_address);

    for(auto &t : threads)
        t.join();
}

int main(int argc,char * argv[])
{

    cout<<"main\n";

    test_singleton();

}

工廠模式

  • 定義一個用於建立物件的介面,讓子類決定例項化哪一個類。

  • Factory Method使得一個類的例項化延遲(目的:解耦,手段:虛擬函式)到子類

  • 在軟體系統中,經常面臨著建立物件的工作;由於需求的 變化,需要建立的物件的具體型別經常變化

    使用工廠模式提供一種“封裝機制”來避免客戶程式和這種“具 體物件建立工作”的緊耦合 來解決這個問題

應用場景:

  • 資料匯出,匯出為Excel,文字,XML
  • 支付介面,可能對應不同的支付閘道器

寫一個工廠模式的demo

#include <iostream>

using namespace std;


//工廠模式 -- 模擬一個簡s單檔案的解析方式
//定義一個產品的概念
class Parse_file{
public:
    Parse_file(){}
    //定義虛解構函式,防止父類指標指向子類物件後,釋放記憶體出現記憶體洩露的問題
    virtual ~Parse_file(){}
    //定義一個介面,子類負責實現
    virtual bool myparse(string data) = 0;

};

//定義實際的產品 text方式解析
class text_parse : public Parse_file{
public:
    text_parse(){}
    virtual ~text_parse(){}

    virtual bool myparse(string data){
        cout<<"以 text 的方式儲存 資料"<<data<<endl;
        return true;
    }

};


//定義實際的產品 xml方式解析
class xml_parse : public Parse_file{
public:
    xml_parse(){}
    virtual ~xml_parse(){}

    virtual bool myparse(string data){
        cout<<"以 xml 的方式儲存 資料"<<data<<endl;
        return true;
    }
};


//定義實際的產品 json方式解析
class json_parse : public Parse_file{
public:
    json_parse(){}
    virtual ~json_parse(){}

    virtual bool myparse(string data){
        cout<<"以 json 的方式儲存 資料"<<data<<endl;
        return true;
    }
};


//定義實際的產品 protobuf方式解析
class protobuf_parse : public Parse_file{
public:
    protobuf_parse(){}
    virtual ~protobuf_parse(){}

    virtual bool myparse(string data){
        cout<<"以 protobuf 的方式儲存 資料"<<data<<endl;
        return true;
    }
};

//定義工廠來生產產品
class factory{
public:
    factory(){}
    virtual ~factory(){}

    //定義工廠的解析方法
//便於子類繼承
    virtual bool myparse(int type,string data){
        Parse_file * pp = nullptr;
        pp = parse_method(type);

        int ret = false;

        if(pp){
            pp->myparse(data);
            delete pp;
            ret = true;
        }
        else{
            cout<<"no parse function\n";
        }
        return ret;
    }
protected:
//便於子類繼承
    virtual Parse_file * parse_method(int type){
        Parse_file * pp = nullptr;
        if(type == 1){
            pp = new text_parse();
        }else if(type == 2){
            pp = new xml_parse();
        }
        return pp;
    }
};

//擴充套件工廠
class factory2 : public factory{
public:
    factory2(){}
    virtual ~factory2(){}

protected:
//便於子類繼承
    virtual Parse_file * parse_method(int type){
        Parse_file * pp = nullptr;
        if(type == 3){
            pp = new json_parse();
        }else if(type == 4){
            pp = new protobuf_parse();
        }
        else{
            pp = factory::parse_method(type);
        }
        return pp;
    }
};

int main()
{
    factory * fac = new factory();
    fac->myparse(1,"資料");
    fac->myparse(2,"資料");
    fac->myparse(3,"資料");
    fac->myparse(4,"資料");

    cout<<"\n\n-----------------\n\n";


    factory * fac2 = new factory2();

    fac2->myparse(1,"資料");
    fac2->myparse(2,"資料");
    fac2->myparse(3,"資料");
    fac2->myparse(4,"資料");

    return 0;
}

效果

img

釋出訂閱模式與觀察者模式

釋出訂閱模式和觀察者模式是同一個東西嗎? NONONO

  • 觀察者模式裡,只有兩個角色 —— 觀察者 + 被觀察者
  • 釋出訂閱模式裡 —— 觀察者 + 中間經紀人 +被觀察者

觀察者模式中的推模型和拉模型:

推模型:

目標物件主動向觀察者推送目標的詳細資訊,不 管觀察者是否需要,推送的資訊通常是目標物件的全部或 部分資料,相當於廣播通訊。

拉模型:

目標物件在通知觀察者的時候,只傳遞少量的信 息。如果觀察者需要更具體的資訊,由觀察者主動到目標 物件中獲取,相當於是觀察者從目標物件中拉資料。一般 這種模型的實現中,會把目標物件通過update方法傳遞給 觀察者,這樣在觀察者需要獲取資料的時候,就可以通過 這個引用來獲取了。

應用場景:

​ 公眾號通知,淘寶通知,知乎通知,微信通知等等。

寫一個觀察者模式的demo

//觀察者模式,需要弄明白 何為觀察者,何為目標
//以我們用手機看報紙為例, 我們 是觀察者, 報紙是目標
//接下來我們來模擬一下觀察者模式

#include <iostream>
#include <list>

using namespace std;

class subject;

//定義抽象的觀察者
class observer{

public:
    observer(){}
    virtual ~observer(){}

    virtual void update(subject * sub) = 0;//讀摘要
    virtual void update(string content) = 0;//讀內容

};

//定義一個抽象的目標
class subject{

public:
    subject(){}
    virtual ~subject(){}
//設定內容
    virtual int setContent(string content)=0;
//得到具體內容 -- 用於推模型
    virtual string getContent()=0;
//得到摘要 -- 用於拉模型
     virtual string getSummary()=0;
//訂閱
    virtual void attach(observer * ob){
        oblist.push_back(ob);
    }
//取消訂閱
    virtual void detach(observer * ob){
        oblist.remove(ob);
    }
//通知所有訂閱者 -- 推模型
    virtual void notifyAllobserver(string content) {
        for(auto &a : oblist){
            a->update(content);
        }
    }
//通知所有訂閱者 -- 拉模型    
    virtual void notifyAllobserver(){
        for(observer * reader : oblist){
            reader->update(this);
        }
    }
private:
    list<observer *> oblist;

};


//定義具體的 觀察者,讀者
class reader: public observer
{
public:
    reader(){}
    virtual ~reader(){}
//拉模型
    virtual void update(subject * sub){
        cout<<getName()<<"正在閱讀的內容是:"<<sub->getContent()<<endl;
    }
//推模型
    virtual void update(string content){
        cout<<getName()<<"正在閱讀的內容是:"<<content<<endl;
    }

    string getName(){return m_name;}
    void setName(string name){m_name = name;}
private:
    string m_name;

};


//定義具體的目標,推送新聞資訊
class newspaper:public subject
{
public:
    newspaper(){};
    virtual ~newspaper(){}

//設定內容
    virtual int setContent(string content){
        m_content = content;
        notifyAllobserver(); //預設是拉模型,就想給你推送一個摘要一樣
        return 1;
    }
//得到具體內容 -- 用於推模型
    virtual string getContent(){
        return m_content;
    }
//得到摘要 -- 用於拉模型
     virtual string getSummary(){
         return "摘要";
     }


private:
    string m_content;
};



int main(int argc,char *argv[])
{

    //定義報紙主題
    newspaper *subject = new newspaper();

    //定義讀者
    reader * r1 = new reader();
    r1->setName("adele");

    reader * r2 = new reader();
    r2->setName("Bob");

    reader * r3 = new reader();
    r3->setName("ceilina");


    //設定內容

    //報紙開始加入訂閱者
    subject->attach(r1);
    subject->setContent("今天多雲");
    cout << "\n----------華麗的分割線 \n"<<endl;


    subject->attach(r2);
    subject->setContent("今天晴天");
    cout << "\n----------華麗的分割線 \n"<<endl;

    subject->attach(r3);
    subject->setContent("over");


    cout<<"-------end-----\n";


    return 0;
}

效果

img

作者:小魔童哪吒

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章