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)需要实例化的类是在运行时刻动态指定时,可以使用原型模式。