• 第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;
    }
    View Code

    (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;
    }
  • 相关阅读:
    个人作业1——四则运算题目生成程序
    软件工程的实践项目课程的自我目标
    个人附加作业
    个人作业3——个人总结(Alpha阶段)
    结对编程2——单元测试
    个人作业二——英语学习APP 案例分析
    结对编程 (201421123002,201421123006,201421123007)
    四则运算
    软件工程的实践项目课程的自我目标
    个人作业3——个人总结(Alpha阶段)
  • 原文地址:https://www.cnblogs.com/5iedu/p/5508332.html
Copyright © 2020-2023  润新知