第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;
}