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