1. 程式人生 > >設計模式 c++版(9)——原型模式

設計模式 c++版(9)——原型模式

定義:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

示例一:個性化電子賬單

1. 需求說明:

銀行傳送電子賬單的郵件一般是有要求的:①個性化服務:發過去的郵件上總有一些個人資訊,比如姓氏等。②遞送成功率:若大批量地傳送郵件會被收房郵件伺服器誤認為是垃圾郵件,因此在郵件頭要增加一些偽造資料,以規避被反垃圾郵件引擎誤認為是垃圾郵件

從這兩方面考慮,電子賬單系統(電子賬單系統一般包括:賬單分析、賬單生成器、廣告信管理、傳送佇列管理、傳送機、退信處理、報表管理等)的一個子功能,現在考慮一下廣告新這個模組如何開發。既然是廣告信,肯定需要一個模板,然後再從資料庫中把客戶的資訊一個一個地取出,放到模板中生成一份完整的郵件,然後扔給傳送機進行傳送處理

2. 個性化電子賬單,類圖13-1

3. 個性化電子賬單,類圖說明

AdvTemplate 是廣告信的模板,一般都是從資料庫取出,生成一個BO或是DTO,我們這裡使用一個驚天的值來代表 Mail 類是一封郵件類,傳送機傳送的就是這個類

4. 個性化電子賬單,程式碼清單13-1:  

////////    **********  1. 傳送電子賬單,程式碼清單13-1:***************//


class AdvTemplate
{
public:
    AdvTemplate()
    {
        this->m_advSubject = "The National Day lottery";
        this->m_advContext = "***********";
    }
    QString getAdvSubject()
    {
        return this->m_advSubject;
    }
    QString getAdvContext()
    {
        return this->m_advContext;
    }

private:
    QString m_advSubject;  //title
    QString m_advContext;  //text
};

class Mail
{
public:
    Mail(){}
    Mail(AdvTemplate advTemplate)
    {
        this->m_contxt = advTemplate.getAdvContext();
        this->m_subject = advTemplate.getAdvSubject();
    }
    QString getReceiver()
    {
        return this->m_receiver;
    }
    void    setReceiver(QString receiver)
    {
        this->m_receiver = receiver;
    }
    QString getSubject()
    {
        return this->m_subject;
    }
    void    setSubject(QString subject)
    {
        this->m_subject = subject;
    }
    QString getAppellation()
    {
        return this->m_appellation;
    }
    void    setAppellation(QString appellation)
    {
        this->m_appellation = appellation;
    }
    QString getContxt()
    {
        return this->m_contxt;
    }
    void    setContxt(QString contxt)
    {
        this->m_contxt = contxt;
    }
    QString getTail()
    {
        return this->m_tail;
    }
    void    setTail(QString tail)
    {
        this->m_tail = tail;
    }

private:
    QString m_receiver;
    QString m_subject;
    QString m_appellation;
    QString m_contxt;
    QString m_tail;
};

static int MAX_COUNT = 6;

class Client
{
public:
    Client()
    {
        AdvTemplate temp;
        this->m_mail = Mail(temp);
        this->m_mail.setTail("copyright");     
        this->m_mail.setAppellation(getString(5));
        this->m_mail.setReceiver(getString(5) + "@");       
    }
    void sendMail()
    {
        for (int i = 0; i < MAX_COUNT; ++i)
        {
            this->m_mail.setAppellation(getString(5));
            this->m_mail.setReceiver(getString(5) + "@");
            qDebug() << "title:" + m_mail.getSubject();
            qDebug() << "receiver:" + m_mail.getReceiver();
        }
    }

    static QString getString(int maxLength)
    {
        QString source = "abcdefghijklmn";
        QString str = source.left(maxLength);
        return str;
    }
private:
    Mail m_mail;
};

int main()
{
    Client client;
    client.sendMail();
    return 0;
}

5. 程式碼分析

這是一個執行緒在執行,也就是傳送的是單執行緒的,那按照一封郵件發出去需要0.02秒,600萬封郵件需要33個小時,也就是一整天都發送不完。在此做修改,把sendMail修改為多執行緒,但是隻把sendMail修改為多執行緒還是有問題,產生第一封郵件物件,放到執行緒1中執行,還沒有傳送出去;執行緒2也啟動了,直接把郵件物件mail的收件人地址和稱謂修改掉了,執行緒不安全了。這裡我們使用一種新型模式來解決這個問題:通過物件的複製功能來解決。

示例二:修改後的傳送電子賬單

1. 類圖,類圖13-2

2. 程式碼清單13-2:  


////////    **********  2. 修改後的傳送電子賬單,程式碼清單13-2:***************//

class Mail;
class Cloneable
{
public:
    virtual Mail clone() = 0;
};

class AdvTemplate
{
public:
    AdvTemplate()
    {
        this->m_advSubject = "The National Day lottery";
        this->m_advContext = "***********";
    }
    QString getAdvSubject()
    {
        return this->m_advSubject;
    }
    QString getAdvContext()
    {
        return this->m_advContext;
    }

private:
    QString m_advSubject;  //title
    QString m_advContext;  //text
};

class Mail:public Cloneable
{
public:
    Mail(){}
    Mail(AdvTemplate advTemplate)
    {
        this->m_contxt = advTemplate.getAdvContext();
        this->m_subject = advTemplate.getAdvSubject();
    }
    virtual Mail clone()
    {
        Mail mail;
        mail.setAppellation(this->m_appellation);
        mail.setContxt(this->m_contxt);
        mail.setReceiver(this->m_receiver);
        mail.setSubject(this->m_receiver);
        mail.setTail(this->m_tail);
        return mail;
    }

    QString getReceiver()
    {
        return this->m_receiver;
    }
    void    setReceiver(QString receiver)
    {
        this->m_receiver = receiver;
    }
    QString getSubject()
    {
        return this->m_subject;
    }
    void    setSubject(QString subject)
    {
        this->m_subject = subject;
    }
    QString getAppellation()
    {
        return this->m_appellation;
    }
    void    setAppellation(QString appellation)
    {
        this->m_appellation = appellation;
    }
    QString getContxt()
    {
        return this->m_contxt;
    }
    void    setContxt(QString contxt)
    {
        this->m_contxt = contxt;
    }
    QString getTail()
    {
        return this->m_tail;
    }
    void    setTail(QString tail)
    {
        this->m_tail = tail;
    }

private:
    QString m_receiver;
    QString m_subject;
    QString m_appellation;
    QString m_contxt;
    QString m_tail;
};

static int MAX_COUNT = 6;

class Client
{
public:
    Client()
    {
        AdvTemplate temp;
        this->m_mail = Mail(temp);
        this->m_mail.setTail("copyright");     
        this->m_mail.setAppellation(getString(5));
        this->m_mail.setReceiver(getString(5) + "@");       
    }
    void sendMail()
    {
        for (int i = 0; i < MAX_COUNT; ++i)
        {
            Mail mail = this->m_mail.clone();
            mail.setAppellation(getString(5));
            mail.setReceiver(getString(5) + "@");
            qDebug() << "title:" + mail.getSubject();
            qDebug() << "receiver:" + mail.getReceiver();
        }
    }

    static QString getString(int maxLength)
    {
        QString source = "abcdefghijklmn";
        QString str = source.left(maxLength);
        return str;
    }
private:
    Mail m_mail;
};

int main()
{
    Client client;
    client.sendMail();
    return 0;
}

3.程式碼說明:

在這裡即使sendMail即使是多執行緒也沒有關係。在Client類中,mail.clone() 這個方法,把物件複製一份,產生一個新的物件,和原有物件一樣,然後再修改細節的資料。不通過new關鍵字來產生一個物件,而是通過物件複製來實現的模式叫做原型模式。

示例三:通用原型模式

1. 類圖13-3

2.  程式碼清單13-3:

class PrototypeClass;
class Cloneable
{
public:
    virtual PrototypeClass clone() = 0;
};

class PrototypeClass:public Cloneable
{
public:
    virtual PrototypeClass clone()
    {
        PrototypeClass proto;
        //copy
        return proto;
    }
};

三、原型模式的應用

1. 優點:

 效能優良。原型模式是在記憶體二進位制流的拷貝,比直接new一個物件效能好很多,特別死要在一個迴圈體內產生大量的物件時,原型模式可以更好地體現其優點  逃避建構函式的約束。直接在記憶體中拷貝,建構函式是不會執行的。

2. 使用場景:

 資源優化場景。類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。  效能和安全要求的場景。通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。  一個物件多個修改者的場景。一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。

3. 注意事項:

物件拷貝時建構函式沒有被執行,Object類的clone 方法的原理是從記憶體中(具體地說就是堆記憶體)以二進位制流的方式進行拷貝,重新分配一個記憶體塊,那建構函式沒有被執行也很正常了。

四、最佳實踐

原型模式先產生出一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節資訊,建立了一個完整的個性物件。

參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社