1. 程式人生 > >第7章 建立型模式—原型模式

第7章 建立型模式—原型模式

1. 原型模式(Prototype pattern)的定義

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

  ①通過克隆來建立新的物件例項

  ②新的物件例項複製原型例項屬性的值

(2)原型模式的結構和說明

  ①Prototype:宣告一個克隆自身的介面,用來約束想要克隆自己的類,要求他們都要實現這裡定義的克隆方法。

  ②ConcretePrototype:實現Prototype介面的類,這些類真正實現了隆自身的功能。

  ③Client:使用原型的客戶端,首先要獲取到原型例項物件,然後通過原型例項的克隆自身來建立新的物件例項。

(3)思考原型模式

  ①原型模型的本質克隆生成物件

  ②原型模式可以用來解決“只知介面而不知實現的問題”,出現一種“介面造介面”的假象。

  ③原型模式的重心還是在建立新的物件例項。至於創建出來的物件,其屬性的值是否一定要和原型物件完全一樣,這並沒有強制規定,但一般會拷貝成一樣的。

  ④通過克隆出來例項是原型例項是兩個完全獨立的例項,他們之間沒有關聯。

【程式設計實驗】訂單拆分處理

//建立型模式:原型模式
//訂單處理:
/*
    功能需求:因每個工作小組的處理能力上限是1000,現要求每當訂單預定產品數量超過1000時,
把訂單拆分為兩份來儲存,如果還是超過1000,那就繼續拆分,直到不超過1000.
*/
#include <iostream>
#include <string>
#include <sstream>

using namespace std;
//*************************輔助類:************************
//定義產品原型的介面,這個產品是為了演示深拷貝
class ProductPrototype
{
public:
    virtual ProductPrototype* clone() = 0;
};

class Product :public ProductPrototype
{
private:
    string productId; //產品編號
    string name;      //產品名稱
public:
    string& getName(){return name;}
    void setName(string name){this->name = name;}
    
    string& getProductId(){return productId;}
    void setProductId(string productId){this->productId = productId;}
    
    string toString()
    {
        return "ProductId="+productId+", productName="+name;
    }
    
    //克隆方法
    ProductPrototype* clone()
    {
        //建立一個新的訂單,然後把本例項的資料複製過去
        Product* product = new Product();
        product->setProductId(productId);
        product->setName(name);
        
        return product;
    }
};

//*************************訂單原型**************************

//訂單的介面,聲明瞭可以克隆自身的方法
class OrderApi
{
public:
    virtual string toString() = 0;
    virtual int getOrderProductNum()=0;
    virtual void setOrderProductNum(int num) = 0;
    virtual OrderApi* clone() = 0;    
};

//個人訂單物件
class PersonalOrder : public OrderApi
{
private:
    Product* product;
public:
    PersonalOrder():orderProductNum(0){product = NULL;}
    
    string toString()
    {
        ostringstream oss;
        oss << orderProductNum;
        return ("PersonalOrder's Order="+customerName+" "+
                                "productName="+product->getName()+" "+
                                "productId="+product->getProductId()+" "+
                                "OrderNum="+oss.str());
    }
    
    OrderApi* clone()
    {
       PersonalOrder* order = new PersonalOrder();
       order->setName(customerName);
       order->setProduct((Product*)product->clone());//深度克隆
       order->setOrderProductNum(orderProductNum);
       return order; 
    }
    
    int getOrderProductNum()
    {
        return orderProductNum;
    }
    
    void setOrderProductNum(int num)
    {
        orderProductNum = num;
    }
    
    string& getName()
    {
        return customerName;
    }
    
    void setName(string name)
    {
        customerName = name;
    }
    
    Product* getProduct(){return product;}
    void setProduct(Product* product)
    {
        this->product = product;
    }
    
private:
    string customerName;
    string productId;
    int orderProductNum;    
};

//企業訂單物件
class EnterpriseOrder : public OrderApi
{
private:
    Product* product;
public:
    EnterpriseOrder():orderProductNum(0){product = NULL;}
    
    string toString()
    {
        ostringstream oss;
        oss << orderProductNum;
        return ("EnterpriseOrder's Order="+enterpriseName+" "
                                "productName="+product->getName()+" "+
                                "productId="+product->getProductId()+" "+
                                "OrderNum="+oss.str());
    }
    
    OrderApi* clone()
    {
       EnterpriseOrder* order = new EnterpriseOrder();
       order->setName(enterpriseName);
       order->setProduct((Product*)product->clone());
       order->setOrderProductNum(orderProductNum);
       return order; 
    }
    
    int getOrderProductNum()
    {
        return orderProductNum;
    }
    
    void setOrderProductNum(int num)
    {
        orderProductNum = num;
    }
    
    string& getName()
    {
        return enterpriseName;
    }
    
    void setName(string name)
    {
        enterpriseName = name;
    }

    Product* getProduct(){return product;}
    void setProduct(Product* product)
    {
        this->product = product;
    }
    
private:
    string enterpriseName;
    string productId;
    int orderProductNum;    
};

//*********************************訂單拆分過程********************
//處理訂單
class OrderBusiness
{
public:
    //saveOrder傳入的是訂單介面型別的物件例項,這裡只知道
    //訂單介面的型別,並不知道其具體型別是個人訂單還是企業訂單
    
    void saveOrder(OrderApi& order)
    {
        //1:判斷當前的預定產品數量是否大於1000
        while(order.getOrderProductNum()> 1000)
        {
            //2.如果大於,還需要繼續拆分
           
            //2.1 再新建一份訂單,跟傳入的訂單除了數量不一樣外,
            //其他都相同            
            //如果不採用克隆的方式,下面這行是不知道如何new一個
            //物件的,因為order只是個介面,不能直接例項化。而
            //Clone的作用在執行時order這個具體的物件是知道自己的型別的
            //所以可以通過自身克隆出一個新的物件。
            OrderApi* newOrder = order.clone();
            
            //然後進行賦值,產品數量為1000
            newOrder->setOrderProductNum(1000);
            
            //2.2 原來的訂單保留,把數量減少1000
            order.setOrderProductNum(order.getOrderProductNum()-1000);
            
            //然後是業務處理功能,省略了,列印輸出看一下
            cout << "split order="+newOrder->toString()<<endl;
        }
        
        //3.不超過,那就直接業務功能處理,省略了,列印輸出看一下
        cout << "order="+order.toString()<<endl;
    }    
};

int main()
{
    //客戶端呼叫例子
 
    //建立訂單物件,這裡為了演示簡單,直接new了
    PersonalOrder* op = new PersonalOrder();
    //EnterpriseOrder* op = new EnterpriseOrder();
    
    //設定產品
    Product* product = new Product();
    product->setName("Product1");
    product->setProductId("P0001");
  
    //設定訂單資料
    op->setProduct(product);   
    op->setOrderProductNum(2925);
    op->setName("SantaClaus");

    
    //這裡獲取業務處理的類,也直接new了
    OrderBusiness* ob = new OrderBusiness();

    // //呼叫業務來儲存訂單物件
    ob->saveOrder(*op);    
    
    return 0;
}

(4)原型模式的主要功能:通過克隆來建立新的物件例項。

  ①原型模式從某種意義上說,是new操作。但只是“類似於new”,而不是“就是new”。因為new一個物件例項,一般屬性是沒有值或只有預設值;而克隆一個例項,通常與原型物件的屬性值是一樣的。

  ②原型例項和克隆例項本質上是兩個不同的例項,它們之間是沒有關聯的。即一個例項的屬性值發生改變,不會影響另一個例項。

2. 淺度克隆和深度克隆

(1)淺度克隆:只負責克隆按值傳遞的數值

(2)深度克隆:除了淺度克隆要克隆的值外,還負責克隆指標所指物件的資料。

3. 原型模式的優缺點

(1)優點

  ①對客戶端隱藏具體的實現型別:客戶端只知道原型介面的型別,從而減少了客戶端對這些具體實現型別的依賴。

  ②在執行時動態改變具體的實現型別:原型模式可以在執行期間,由客戶來註冊符合原型介面的實現型別,也可以動態地改變具體的實現型別。表面看起來介面沒有任何變化,但其實執行的己經是另一個類例項了。因為克隆一個原型就類似於例項化一個類。

(2)缺點

  ①每個原型的子類都必須實現clone介面

  ②當原型例項中出現複雜物件時,會遞迴對克隆其他物件。

  ③當原型內部包括一些不支援拷貝的物件時,可以導致克隆失敗。

4.原型模式的使用場景

(1)在建立物件的時候,我們不只是希望被建立的物件繼承其基類的基本結構,還希望繼承原型物件的資料

(2)希望對目標物件的修改不影響既有的原型物件(深度克隆的時候可以完全互不影響)。

(3)建立物件時,只知道介面,可以這克隆原型來得到。

(4)需要例項化的類是在執行時刻動態指定時,可以使用原型模式。

5. 原型模式的擴充套件

(1)需求分析

  ①在很多軟體中都一個繪圖工具箱,裡面有直線、圓形、矩形等繪圖工具。每點一次這個工具箱中的工具,則繪製一個相應的圖。

  ②在軟體實現中,可以先將這些圖形生成一個個帶有預設大小物件(“繪圖工具”),並將他們放入一個叫原型管理器的東西中(類似於工具箱)。以後繪圖時,可以從這個管理器中“拖出”(Clone)這些物件,並改變他們的屬性值以達到繪圖的目的。

(2)原型管理器中各角色

  ①客戶(Client)角色:客戶端類向原型管理器提出建立物件的請求。

  ②抽象原型(Prototype)角色:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體原型類所需的介面。

  ③具體原型(Concrete Prototype)角色:被複制的物件。此角色需要實現抽象的原型角色所要求的介面。

  ④原型管理器(Prototype Manager)角色:建立具體原型類的物件,並記錄每一個被建立的物件。

【程式設計實驗】“繪圖工具箱”的實現

//建立型模式:原型模式
//原型管理器:

#include <iostream>
#include <string>
#include <map>
using namespace std;

//抽象原型角色:Prototype
class DrawPrototype
{
protected:
    //需要繪製的圖形名稱、高和寬
    string m_DrawName;
    double m_Height;
    double m_Width;
    
public:
    virtual DrawPrototype* clone() = 0; //對原型進行克隆
    
    string& getDrawName(){return m_DrawName;}
    void setDrawName(string drawName){m_DrawName = drawName;}
    
    double getHeight(){return m_Height;}
    void setHeight(double height){m_Height = height;}
    
    double getWidth(){return m_Width;}
    void setWidth(double width){m_Width = width;}    
};

//具體原型角色(ConcretePrototype)
class ConcreteDrawing : public DrawPrototype
{
public:
    DrawPrototype* clone()
    {
        DrawPrototype* ret = new ConcreteDrawing;
        ret->setDrawName(this->getDrawName());
        ret->setHeight(this->getHeight());
        ret->setWidth(this->getWidth());
        
        return ret;      
    }   
    
    //顯示自身特性
    void showInfo()
    {
        cout <<"DrawName=" <<m_DrawName << " Height=" << m_Height << 
                " Width=" << m_Width << endl;
    }    
};

//原型管理器角色(PrototypeManager)
class DrawManager
{
    map<string,DrawPrototype*> drawingMap;
public:
    //新增原型到管理器
    void addDrawing(string key,DrawPrototype* dpt)
    {
        drawingMap[key] = dpt;
    }
    
    //獲取到對應名字的原供以供克隆副本
    DrawPrototype* getDrawing(string drawName)
    {
        return drawingMap[drawName];
    }    
};


//客戶端角度(Client)
int main()
{
    //初始化繪畫管理工具
    DrawManager drawManager;
    
    //初始化矩形、圓形、梯形、直線的原型實體以供後面拖出來使用
    
    //矩形
    DrawPrototype* rc = new ConcreteDrawing();
    rc->setDrawName("Rectangle");
    rc->setHeight(100);
    rc->setWidth(100);
    drawManager.addDrawing("Rectangle", rc);
    
    //圓形
    DrawPrototype* cc = new ConcreteDrawing();
    cc->setDrawName("Cricle");
    cc->setHeight(80);
    cc->setWidth(80);
    drawManager.addDrawing("Circle", cc);
    
    //梯形
    DrawPrototype* tz = new ConcreteDrawing();
    tz->setDrawName("Trapezoidal");
    tz->setHeight(50);
    tz->setWidth(50);
    drawManager.addDrawing("Trapezoidal", tz); 

    //直線
    DrawPrototype* ln = new ConcreteDrawing();
    ln->setDrawName("Line");
    ln->setHeight(100);
    ln->setWidth(1);
    drawManager.addDrawing("Line", ln); 

    //呼叫原型的Clone方法獲取淺拷貝物件
    //繪製(拖出)第1個矩形
    ConcreteDrawing* rect1 = (ConcreteDrawing*)drawManager.getDrawing("Rectangle")->clone(); 
    rect1->setHeight(197);
    rect1->showInfo();  
    
    //繪製(拖出)第2個矩形
    ConcreteDrawing* rect2 = (ConcreteDrawing*)drawManager.getDrawing("Rectangle")->clone(); 
    rect2->setWidth(112);
    rect2->showInfo();  

    //繪製(拖出)第3個矩形(預設大小)
    ConcreteDrawing* rect3 = (ConcreteDrawing*)drawManager.getDrawing("Rectangle")->clone(); 
    rect3->showInfo(); 

    //畫線
    ConcreteDrawing* line = (ConcreteDrawing*)drawManager.getDrawing("Line")->clone(); 
    line->showInfo();         
    
    return 0;
}