C++设计模式之原型模式

来源:互联网 发布:极品飞车ol mac 编辑:程序博客网 时间:2024/05/21 10:19

       看到上图中的百元大钞、心里总在想这Money是我的那该多好。仔细看这些百元大钞,除了编号不同外,其余的信息都是一样的。印刷厂首先有一张100元的钞票,然后把它当作原型,印刷出一叠叠的钞票,流入市场。在设计模式中也存在一个类似的模式,可以根据一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。

1、原型模式 

       在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。如果先没有一张100元的钞票,那又如何产生出一大叠的百元大钞呢?原型模式的定义如下:

原型模式(Prototype  Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

    原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。由于在软件系统中我们经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在真实开发中的使用频率还是非常高的。

    需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过不同的方式修改可以得到一系列相似但不完全相同的对象。

    原型模式的结构如图所示:


原型模式结构图

    在原型模式结构图中包含如下几个角色:

    Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。

    ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

    Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。


2、刻录机的设计与实现

    设计一个刻录机,目前要求能够刻DVD、VCD格式。现考虑可扩展性,将来可能还需要支持其它格式的刻录操作。

1.不使用模式实现方式

    .h头文件代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #ifndef _RECORDE_H_  
  2. #define _RECORDE_H_  
  3. #include <iostream>  
  4. #include <string>  
  5. using namespace std;  
  6.   
  7. //抽象刻录类  
  8. class Recorde  
  9. {  
  10. private:  
  11.     string m_strContex;  
  12. public:  
  13.     //设置需要刻录的内容  
  14.     void SetContex(string strContex);  
  15.       
  16.     //获取刻录的内容  
  17.     string GetContex();  
  18. };  
  19.   
  20. //DVD类  
  21. class DVD : public Recorde  
  22. {  
  23.   
  24. };  
  25.   
  26. //VCD类  
  27. class VCD : public Recorde  
  28. {  
  29.   
  30. };  
  31. #endif  

    cpp实现代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include "Recorde.h"  
  2.   
  3. //设置需要刻录的内容  
  4. void Recorde::SetContex(string strContex)  
  5. {  
  6.     m_strContex = strContex;  
  7. }  
  8.   
  9. //获取刻录的内容  
  10. string Recorde::GetContex()  
  11. {  
  12.     return m_strContex;  
  13. }  

    Recorde为抽象刻录类,只包含设置刻录内容和获取刻录内容方法。DVD和VCD是Recorde的子类,负责对具体内容进行刻录操作。

    测试代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include "Recorde.h"  
  3.   
  4. using namespace std;  
  5.   
  6.   
  7. int main()  
  8. {  
  9.     //刻录第一张DVD  
  10.     Recorde * pDVD1 = new DVD();  
  11.     pDVD1->SetContex("DVD1");  
  12.   
  13.     //刻录第二张DVD  
  14.     Recorde * pDVD2 = new DVD();  
  15.     pDVD2->SetContex("DVD2");  
  16.       
  17.     //刻录第三张DVD  
  18.     Recorde * pDVD3 = new DVD();  
  19.     pDVD3->SetContex("DVD3");  
  20.   
  21.     //销毁操作  
  22.     delete pDVD1;  
  23.     pDVD1 = NULL;  
  24.   
  25.     delete pDVD2;  
  26.     pDVD2 = NULL;  
  27.   
  28.     delete pDVD3;  
  29.     pDVD3 = NULL;  
  30.   
  31.     return 0;  
  32. }  

    可以发现上面程序能够实现刻录操作、但程序是有问题的。刻录三张DVD,客户端需要显示的调用三次 new DVD()操作, 把创建的具体细节暴露给了客户端。如果需要大量的刻录操作,也需要大量地调用new操作创建各个对象。已经发现问题的所在,需要对刻录操作进行重构。在重构代码之前,在来看个不使用模式实现存在的问题。

2.不使用模式实现方式

    上面的程序直接在客户端(Main函数)中刻录了三张DVD,现在封装一个刻录类,用来刻录DVD,VCD等格式,隐藏刻录的细节。

    执行刻录操作类的实现过程如下:

[cpp] view plaincopy
  1. #ifndef _RECORD_OPERATE_H_  
  2. #define _RECORD_OPERATE_H_  
  3.   
  4. #include "Recorde.h"  
  5.   
  6. //执行刻录操作类  
  7. class RecordOperate  
  8. {  
  9. public:  
  10.     //动态的刻录DVD获取VCD  
  11.     Recorde * ExcuteRecord(Recorde * pRecorde)  
  12.     {  
  13.         DVD * pDVD = NULL;  
  14.         Recorde * pNewRecorde = NULL;  
  15.   
  16.         pDVD = static_cast<DVD *> (pRecorde);  
  17.   
  18.         if( NULL != pDVD )  
  19.         {  
  20.             string strContex = pRecorde->GetContex();  
  21.               
  22.             //需要知道具体刻录类型(违背迪米特法则)  
  23.             pNewRecorde = new DVD();  
  24.   
  25.             //需要知道具体刻录类型的方法(违背迪米特法则)  
  26.             pNewRecorde->SetContex(strContex);  
  27.   
  28.             return pNewRecorde;  
  29.         }  
  30.         else  
  31.         {  
  32.             string strContex = pRecorde->GetContex();  
  33.               
  34.             //需要知道具体刻录类型(违背迪米特法则)  
  35.             pNewRecorde = new VCD();  
  36.   
  37.             //需要知道具体刻录类型的方法(违背迪米特法则)  
  38.             pNewRecorde->SetContex(strContex);  
  39.               
  40.             return pNewRecorde;  
  41.         }  
  42.     }  
  43. };  
  44.   
  45.   
  46. #endif  

    测试代码如下:

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include "Recorde.h"  
  3. #include "RecordOperate.h"  
  4.   
  5. using namespace std;  
  6.   
  7.   
  8. int main()  
  9. {  
  10.     //DVD  
  11.     Recorde * pDVD1 = new DVD();  
  12.     pDVD1->SetContex("DVD1");  
  13.     cout << pDVD1->GetContex() << endl;  
  14.       
  15.     //刻录DVD  
  16.     RecordOperate * pRecordOperate = new RecordOperate();  
  17.     Recorde * pDVD2 = pRecordOperate->ExcuteRecord(pDVD1);  
  18.     cout << pDVD2->GetContex() << endl;  
  19.   
  20.     //VCD  
  21.     Recorde * pVCD1 = new VCD();  
  22.     pVCD1->SetContex("pVCD1");  
  23.     cout << pVCD1->GetContex() << endl;  
  24.   
  25.     //刻录VCD  
  26.     Recorde * pVCD2 = pRecordOperate->ExcuteRecord(pVCD1);  
  27.     cout << pVCD1->GetContex() << endl;  
  28.   
  29.     //销毁操作  
  30.     delete pDVD1;  
  31.     pDVD1 = NULL;  
  32.   
  33.     delete pDVD2;  
  34.     pDVD2 = NULL;  
  35.   
  36.     delete pVCD1;  
  37.     pVCD1 = NULL;  
  38.   
  39.     delete pVCD2;  
  40.     pVCD2 = NULL;  
  41.   
  42.     delete pRecordOperate;  
  43.     pRecordOperate = NULL;  
  44.   
  45.     return 0;  
  46. }  
 编译并执行,结果如下:



    RecordOperate是一个刻录类,封装了刻录操作,用于替代客户端(直接在Main函数创建对象,暴露细节)。
ExcuteRecord函数带有一个抽象Recorde类型参数,用于执行刻录操作。但ExcuteRecord函数针对抽象Recorde类型进行编程,在程序执行过程中是无法判断具体需要刻录哪种类型的光盘(DVD还是VCD???),解决方法:使用运行时类型识别static_cast进行强制类型转化。
    此时可以判断出需要刻录何种类型的光盘,但缺乏拓展性,如果需要刻录其它类型的光盘,得修改ExcuteRecord函数,违背了"开放---封闭原则"。

    除违背了"开放---封闭原则"外,ExcuteRecord针对具体类型DVD或者VCD编程。pNewRecorde = new DVD()就是针对DVD这个具体类型进行编程,并且调用了DVD类型的SetContex方法。这违背了迪米特法则(最少朋友原则),即一个类只需要和自己的直接朋友打交道,同时尽可能少的调用朋友类的方法。RecordOerate类需要认识DVD类,VCD类,还得调用他们的SetContex方法。因此不能在RecordOerate中创建具体的DVD或者VCD,应该把创建的过程封装起来,由Record类自己负责创建一个对象,客户端就不需要知道具体的创建过程了。


3.使用设计模式的实现

    头文件的代码如下:

[cpp] view plaincopy
  1. #ifndef _RECORDE_H_  
  2. #define _RECORDE_H_  
  3.   
  4. #include <iostream>  
  5. #include <string>  
  6. using namespace std;  
  7.   
  8.   
  9. //抽象刻录类  
  10. class Recorde  
  11. {  
  12. private:  
  13.     string m_strContex;  
  14. public:  
  15.     //设置刻录内容  
  16.     void SetContex(string strContex);  
  17.   
  18.     //获取刻录内容  
  19.     string GetContex();  
  20.   
  21.     //克隆函数,创建一个自己的实例  
  22.     virtual Recorde * Clone() = 0;  
  23. };  
  24.   
  25.   
  26. //DVD类  
  27. class DVD : public Recorde  
  28. {  
  29. public:  
  30.     Recorde * Clone();  
  31. };  
  32.   
  33.   
  34. //VCD类  
  35. class VCD : public Recorde  
  36. {  
  37. public:  
  38.     Recorde * Clone();  
  39. };  
  40.   
  41. #endif  

    Recorde抽象类中添加了一个纯虚函数Clone,用来克隆一个对象,具体由子类来完成克隆操作。

    cpp实现代码如下:

[cpp] view plaincopy
  1. #include "Recorde.h"  
  2.   
  3.   
  4. //设置刻录内容  
  5. void Recorde::SetContex(string strContex)  
  6. {  
  7.     m_strContex = strContex;  
  8. }  
  9.   
  10.   
  11. //获取刻录内容  
  12. string Recorde::GetContex()  
  13. {  
  14.     return m_strContex;  
  15. }  
  16.   
  17.   
  18. //DVD刻录,创建一个DVD对象  
  19. Recorde * DVD::Clone()  
  20. {  
  21.     Recorde * pDVD = new DVD();  
  22.     string strContex = GetContex();  
  23.   
  24.     pDVD->SetContex(strContex);  
  25.       
  26.     return pDVD;  
  27. }  
  28.   
  29.   
  30. //VCD刻录,创建一个VCD对象  
  31. Recorde * VCD::Clone()  
  32. {  
  33.     Recorde * pVCD = new VCD();  
  34.     string strContex = GetContex();  
  35.   
  36.     pVCD->SetContex(strContex);  
  37.   
  38.     return pVCD;  
  39. }  

    具体子类实现基类的Clone函数,创建一个具体类型的对象,并返回

    测试程序实现如下:

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include "Manager.h"  
  3. #include "Recorde.h"  
  4. using namespace std;  
  5.   
  6.   
  7. int main()  
  8. {  
  9.     //Dvd操作  
  10.     Recorde * pDVD = new DVD();  
  11.     pDVD->SetContex("DVD");  
  12.     cout << "原始:" << pDVD->GetContex() << endl;  
  13.   
  14.     Recorde * pNewDVD = pDVD->Clone();  
  15.     cout << "刻录:" << pNewDVD->GetContex() << endl;  
  16.   
  17.     cout << "************" << endl;  
  18.   
  19.     //VCD操作  
  20.     Recorde * pVCD = new VCD();  
  21.     pVCD->SetContex("VCD");  
  22.     cout << "原始:" << pVCD->GetContex() << endl;  
  23.   
  24.     Recorde * pNewVCD = pVCD->Clone();  
  25.     cout << "刻录:" << pNewVCD->GetContex() << endl;  
  26.   
  27.   
  28.     //销毁工作  
  29.     delete pDVD;  
  30.     pDVD = NULL;  
  31.   
  32.     delete pNewDVD;  
  33.     pNewDVD = NULL;  
  34.   
  35.     delete pVCD;  
  36.     pVCD = NULL;  
  37.   
  38.     delete pNewVCD;  
  39.     pNewVCD = NULL;  
  40.   
  41.     return 0;  
  42. }  
    编译并运行程序,结果如下:


    通过和没有使用模式的程序对比,我们发现使用了模式实现的程序,客户端只需要创建一个原型对象就可以了,如果还需要和原型一模一样的对象,不需要通过New操作实例化一个对象,而是通过调用原型对象的Clone方法。也就是说原型模式封装了对象创建的细节,客户端只需要调用Clone就可以创建一个一模一样的对象,对客户端隐藏了创建的细节。

    由于针对Recorde抽象类进行编程,当需要刻录其他格式的光盘时,只需要定义一个子类,继承Recorde类就可以了,满足"开放---封闭"原则。

4.带有原型管理器的原型模式    

    原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。

    刻录类头文件如下:

[cpp] view plaincopy
  1. #ifndef _RECORDE_H_  
  2. #define _RECORDE_H_  
  3. #include <iostream>  
  4. #include <string>  
  5. using namespace std;  
  6.   
  7. //抽象刻录类  
  8. class Recorde  
  9. {  
  10. private:  
  11.     string m_strContex;  
  12. public:  
  13.     //设置刻录内容  
  14.     void SetContex(string strContex);  
  15.   
  16.     //获取刻录内容  
  17.     string GetContex();  
  18.   
  19.     //克隆函数,创建一个自己的实例  
  20.     virtual Recorde * Clone() = 0;  
  21. };  
  22.   
  23. //DVD类  
  24. class DVD : public Recorde  
  25. {  
  26. public:  
  27.     Recorde * Clone();  
  28. };  
  29.   
  30. //VCD类  
  31. class VCD : public Recorde  
  32. {  
  33. public:  
  34.     Recorde * Clone();  
  35. };  
  36.   
  37. #endif  
    刻录类的cpp实现代码如下:

[cpp] view plaincopy
  1. #include "Recorde.h"  
  2.   
  3. //设置刻录内容  
  4. void Recorde::SetContex(string strContex)  
  5. {  
  6.     m_strContex = strContex;  
  7. }  
  8.   
  9. //获取刻录内容  
  10. string Recorde::GetContex()  
  11. {  
  12.     return m_strContex;  
  13. }  
  14.   
  15. //DVD刻录,创建一个DVD对象  
  16. Recorde * DVD::Clone()  
  17. {  
  18.     Recorde * pDVD = new DVD();  
  19.     string strContex = GetContex();  
  20.   
  21.     pDVD->SetContex(strContex);  
  22.       
  23.     return pDVD;  
  24. }  
  25.   
  26. //VCD刻录,创建一个VCD对象  
  27. Recorde * VCD::Clone()  
  28. {  
  29.     Recorde * pVCD = new VCD();  
  30.     string strContex = GetContex();  
  31.   
  32.     pVCD->SetContex(strContex);  
  33.   
  34.     return pVCD;  
  35. }  
    添加一个原型管理器类Manager,用来保存和获取某个原型对象。当需要克隆某个原型对象的时候,直接从管理器中找出该原型对象,并克隆。

    原型管理器头文件代码如下:

[cpp] view plaincopy
  1. #ifndef _MANAGER_H_  
  2. #define _MANAGER_H_  
  3.   
  4. #include <iostream>  
  5. #include <map>  
  6. #include <string>  
  7. #include "Recorde.h"  
  8.   
  9. using namespace std;  
  10.   
  11. //原型管理器  
  12. class Manager   
  13. {  
  14. private:  
  15.     //存放各种刻录原型对象  
  16.     map< string ,  Recorde * > m_Map;  
  17. public:  
  18.     //往容器中添加原型对象  
  19.     void AddRecorde(string strRecordeType, Recorde * pRecorde);  
  20.   
  21.     //获取原型对象  
  22.     Recorde * GetRecorde(string strRecordeType);  
  23. };  
  24.   
  25. #endif  
    原型管理器cpp实现代码如下:
[cpp] view plaincopy
  1. #include "Manager.h"  
  2.   
  3. //往容器中添加原型对象  
  4. void Manager::AddRecorde(string strRecordeType, Recorde * pRecorde)  
  5. {  
  6.     m_Map.insert(map< string, Recorde* >::value_type(strRecordeType, pRecorde));  
  7. }  
  8.   
  9.   
  10.   
  11. //获取原型对象  
  12. Recorde * Manager::GetRecorde(string strRecordeType)  
  13. {  
  14.     map< string, Recorde* >::iterator ite =  m_Map.find(strRecordeType);  
  15.       
  16.     if( ite != m_Map.end() )  
  17.     {         
  18.         return ite->second;  
  19.     }  
  20. }  
    测试程序代码如下:

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include "Manager.h"  
  3. #include "Recorde.h"  
  4. using namespace std;  
  5.   
  6.   
  7. int main()  
  8. {  
  9.     //创建DVD原型对象  
  10.     Recorde * pDVD = new DVD();  
  11.     pDVD->SetContex("DVD");  
  12.   
  13.     //创建VCD原型对象  
  14.     Recorde * pVCD = new VCD();  
  15.     pVCD->SetContex("VCD");  
  16.       
  17.     //创建原型管理器  
  18.     Manager * pManager = new Manager();  
  19.     Recorde * pRecorde = NULL;  
  20.   
  21.     //往原型管理器中添加DVD,VCD原型对象  
  22.     pManager->AddRecorde("DVD", pDVD);  
  23.     pManager->AddRecorde("VCD", pVCD);  
  24.   
  25.     //从原型管理取出DVD原型对象,并克隆DVD  
  26.     pRecorde = pManager->GetRecorde("DVD");  
  27.     Recorde * pNewDVD = pRecorde->Clone();  
  28.     cout << pNewDVD->GetContex() << endl;  
  29.   
  30.     //从原型管理取出VCD原型对象,并克隆VCD  
  31.     pRecorde = pManager->GetRecorde("VCD");  
  32.     Recorde * pNewVCD = pRecorde->Clone();  
  33.     cout << pNewVCD->GetContex() << endl;  
  34.   
  35.     //销毁操作  
  36.     delete pDVD;  
  37.     pDVD = NULL;  
  38.   
  39.     delete pVCD;  
  40.     pVCD = NULL;  
  41.   
  42.     delete pManager;  
  43.     pManager = NULL;  
  44.   
  45.     delete pNewDVD;  
  46.     pNewDVD = NULL;  
  47.   
  48.     delete pNewVCD;  
  49.     pNewVCD = NULL;  
  50.   
  51.     return 0;  
  52. }  
   编译并运行,结果如下:

    原型管理器针对抽象Recorde编程,刻录类Recorde和原型管理器Manager是一种依赖关系,使用组合的方式实现功能的复用。当需要拓展刻录格式时,只需要定义一个继承Recorde的子类,并把Recorde指针传给原型原理器,就可以实现刻录功能,不需要修改任何代码,符合“开发封闭原则”。


3、原型模式总结

1.主要优点

    原型模式的主要优点如下:

    (1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

    (2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响,符合“开放封闭原则”

    (3) 添加一个原型管理器,抽象原型类和原型管理器类是一种依赖关系,通过类似组合的方式来实现对原型类的复用,符合"多用组合,少用继承原则"。

    (4) 原型模式封装了对象的创建过程,对客户端隐藏了对象创建的具体细节。

2.主要缺点

    原型模式的主要缺点如下:

    (1) 原型类除了业务逻辑外,还存在一个Clone方法,用于克隆自己。一个类存在两种职责,导致类的职责过重,违背"单一原则"。

3.原型模式具体应用

    (1)通常软件开发过程中编写的周报告、月报告、可行性分析报告、软件需求规格说明书、项目进展报告等,都各有一份原型模板,只需要根据这原型模板就可以编写出多份报告。
    (2)在使用手机过程中,经常会收到天气预报,商品促销活动等短信。这些短信都有一份原型,只需要根据原型进行修改,就可以生成多条短信,然后发送给手机用户。
    (3)假定我们要开发一个调色板,用户单击调色板上任一个方块,将会返回一个对应的颜色的实例,这些不同的颜色就是一个原型对象。单击调色板上的任一个方块,将在调色板管理器中找到该颜色对应的原型对象,然后克隆一个该原型对象并返回。
    (4)现在很多音乐播放器,其界面设计风格大同小异。可以这么认为,某款音乐播放器是以另一款音乐播放器的界面为参考原型,设计一个新的播放器。
    (5)在游戏行业中,只要出了一款新游戏。很多游戏公司就以这款游戏为参考原型,设计出一款差不多的新游戏,导致该类型的游戏多种多样。网上随便搜索射击类,赛车类等游戏,基本上都是类似的。
    (6)在OCR识别行业,只要有一家公司做出文档识别,银行卡识别项目抢占了市场,尝尽了甜头。紧接着一大批OCR公司会以这些成功的产品为原型,开发出类似的文档识别、银行卡识别项目,在市场上占一席之地。
    (7)上海通用需要为自己的汽车产品定制导航功能,可以和高德地图合作,为该导航定制一份原型。需要量产的时候,只需要把这原型安装到各车辆上。
    (8)自从微软推出Office办公软件之后,好些公司仿造Office,制作出一些新的办公软件。如WPS。
    (9)我们网上购物的时候,会让我们填写邮寄姓名和地址,我们不需要再次输入姓名和邮寄地址,可以选择之前保存的信息。同样的,在12306购买火车票的时候,也可以从联系人列表中选择需要乘车的客人。这些都是原型模式的应用。
    (10)在软件开发过程中,也可以为需要开发的软件制定一个原型,之后可以根据这个原型进行迭代开发。
    (11)生活中也有原型模式的例子:配置一把和现在一模一样的钥匙;山寨一款产品(例如山寨IPHONE);欣赏某栋别墅的设计风格,自己仿照这栋别墅的设计风格,设计出一栋一模一样的别墅;当你想要为自己的手机定制一块电池的时候,拿着原装电池去手机店就可以了;模具工厂根据需要生产的模具原型,批量生产该类型模具;小时候老师布置作业,要求我们把文章抄写10遍,不爱学习的我抄写一遍之后就使用复印纸了;京剧各种角色的变化;细胞分裂;克隆技术等。




0 0
原创粉丝点击