feipeng

不要过分强调技术,思想才是关键!
  新随笔  :: 管理

设计模式--Prototype

Posted on 2007-03-13 10:37  FrankFei  阅读(13003)  评论(3编辑  收藏  举报
                                                                          FrankFei,2007年3月13日


一、引言
在商品房销售系统中,房屋信息是基础信息。在系统运行前必须输入房屋的各种信息到系统中,这是一项枯燥的重复劳动。如果让用户重复输入房间的类型、面积和卫生间样式,这个系统肯定尚未运行就夭折了。实际上,一个小区楼盘的样式并不多,不同的只是楼号。另外,楼盘中的房间类型也非常有限,从而为解决输入问题提供了启示。楼盘的逻辑结构如图所示。
一个小区包含多个楼盘,一个楼盘包括多层,一层包括多个房间。楼盘和房间都有自己的样式(这个模型忽略高档住宅和别墅),要解决的问题是如何创建这些类的实例,这些实例内部的数据基本相同。
方案一:手工输入,根据录入的数据实例化对象。
这个方案在增加用户痛苦的同时,并未给程序员带来更多的好处,不予考虑。
方案二:由用户输入一些基本信息和规则,由程序来实例化对象。
系统执行过程如下。
1、用户选择新增一个楼盘,系统创建楼盘实例。
2、用户输入一个房间的基本信息和需要自动生成的楼层,系统实例化这些信息,
然后增加到楼盘中。
3、用户重复第2步,直到输入整个楼盘的数据。
这个方案中的用户操作已经减少了很多,但仍有问题,如果一个小区的各个楼盘结构完全一样,那么如何自动生成?为此在用户界面上需要增加生成楼盘组,然后输入需要生成的数量及房间信息等,这样使得系统变得非常不灵活,无法扩充。例如,需要建立一个基本类似的小区。并且也很难去掉某些功能,例如不需要自动生成楼盘组。如果自动生成出现问题,例如房号的规则输入错误,则必须销毁对象后重新处理。虽然系统中已经存在一个现成的楼盘(例如一幢已经销售完成的楼盘与新楼盘结构一样),但是却无法使用。
从程序角度更是难以维护,每新增一级输入,都需要改动创建对象的工厂方法。
不幸的是,这种方案在重复输入信息的系统中经常被采用,从而导致系统变得难以维护。
方案三:拷贝/粘贴。
从用户的角度看,系统最好支持拷贝/粘贴。例如复制一个现成的楼盘,粘贴后只要修改楼号即可,或者复制多个楼层粘贴后自动增加到楼层中,这样用户的交互过程要简单很多。
 
 
二、概述
在软件系统中,有时候面临的产品类是动态变化的,而且这个产品类具有一定的等级结构。这时如果用工厂模式,则与产品类等级结构平行的工厂方法类也要随着这种变化而变化,显然不大合适。那么如何封装这种动态的变化?从而使依赖于这些易变对象的客户程序不随着产品类变化?
 
三、意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
 
四、Portotype模式的结构
Prototype:声明一个克隆自身的接口。
ConcretePrototype:实现一个克隆自身的操作。
Client:让一个原型克隆自身从而创建一个新的对象。
 
五、举例
例子一:个人觉得吕震宇老师的例子很容易理解,故直接引用如下:
// Prototype pattern -- Structural example  
using System;

// "Prototype"
abstract class Prototype
{
  
// Fields
  private string id;

  
// Constructors
  public Prototype( string id )
  
{
    
this.id = id;
  }


  
public string Id
  
{
    
getreturn id; }
  }


  
// Methods
  abstract public Prototype Clone();
}


// "ConcretePrototype1"
class ConcretePrototype1 : Prototype
{
  
// Constructors
  public ConcretePrototype1( string id ) : base ( id ) {}

  
// Methods
  override public Prototype Clone()
  
{
    
// Shallow copy
    return (Prototype)this.MemberwiseClone();
  }

}


// "ConcretePrototype2"
class ConcretePrototype2 : Prototype
{
  
// Constructors
  public ConcretePrototype2( string id ) : base ( id ) {}

  
// Methods
  override public Prototype Clone()
  
{
    
// Shallow copy
    return (Prototype)this.MemberwiseClone();
  }

}


/// <summary>
/// Client test
/// </summary>

class Client
{
  
public static void Main( string[] args )
  
{
    
// Create two instances and clone each
    ConcretePrototype1 p1 = new ConcretePrototype1( "I" );
    ConcretePrototype1 c1 
= (ConcretePrototype1)p1.Clone();
    Console.WriteLine( 
"Cloned: {0}", c1.Id );

    ConcretePrototype2 p2 
= new ConcretePrototype2( "II" );
    ConcretePrototype2 c2 
= (ConcretePrototype2)p2.Clone();
    Console.WriteLine( 
"Cloned: {0}", c2.Id );
  }

}
 
例子二:
1、.NET已经为实现提供了必要的基础,即System命名空间中的ICloneable接口。这个接口只有Clone这一个方法。实现这个接口的类也就具备了Clone方法,实现克隆还需要我们编写代码。
2、实现结构:假如我们需要一个计算机类,这个类的属性包括品牌、CPU、RAM和硬盘容量等。如果克隆同配置的计算机,并希望这个类可以成为原型,则代码如下:
public class Computer : ICloneable
{
    
private string m_CPU;
    
private string m_Brand;
    
private string m_RAM;
    
private string m_HardDisk;
    
private string m_Number;
    
public string CPU
    
{
        
get
        
{
            
return m_CPU;
        }

    }

    
public string Brand
    
{
        
get
        
{
            
return m_Brand;
        }

    }

    
public string RAM
    
{
        
get
        
{
            
return m_RAM;
        }

    }

    
public string HardDisk
    
{
        
get
        
{
            
return m_HardDisk;
        }

    }

    
public Computer Clone()
    
{
        Computer computer;
        computer.CPU 
= m_CPU;
        computer.Brand 
= m_Brand;
        computer.RAM 
= m_RAM;
        computer.HardDisk 
= m_HardDisk;
        
return computer;
    }

}

这段代码来源于设备管理系统,并进行了简化。注意在复制代码时并没有复制Number属性,这是因为该属性是用来区别计算机的设备号,不能重复,因此应该由客户程序赋值或者由序号发生器生成。
3、深复制与浅复制
如果仅仅从示例代码上看,实现原型模式并不困难。然而复杂的情况是,如果一个对象是复合对象或者包含其他的对象,那么克隆对象的问题是仅仅复制对象本身,还是连同被引用的对象一起复制?
例如,我们在克隆一个楼盘时,希望获得关于这个楼盘的所有新的数据,以便于修改。这时在复制楼盘时,复制了楼盘对象及其包含的所有房间对象,并且隶属于新产生的楼盘。在这个操作中,房间的复制是间接进行的,这种情况是深复制。即除了引用其他对象的变量外,被复制对象的所有变量都含有与原对象相同的值,引用其他对象的变量将指向原有这些变量的复制品。
有时我们不希望复制包含的对象,而是需要所复制对象中包含的对象仍然指向原来的引用。例如,克隆用户清单,其中包括了用户对象,而克隆的用户清单不需要复制所包含的用户对象。这样不管修改了哪个清单中的用户信息,都可以在另一个清单中得到反应,这种情况是浅复制。即被复制对象的所有变量都含有与原对象相同的值,换言之,所有引用其他对象的变量仍然指向原来引用的对象。
 
从下面这幅图可以很容易的区别出浅拷贝和深拷贝,当在进行浅拷贝时,指针还是指向原来Value地址中的值,当进行深拷贝时,系统会把原来Value的地址即所包含的值都进行复制。

 
六、优点
1、在运行时增加或删除产品:只要通过客户原型实例即可将新产品类型增加到系统中,例如组态软件中工具箱中的每个工具可以对应一个注册的原型对象,可以通过增加原形对象扩展工具箱。
2、很容易地创建复杂的对象:在图形编辑和组态等软件中,经常需要创建复杂的图元。这些图元是由简单图元组成的,采用原型模式可以很容易地将复杂图元作为一般图元来使用,使软件的工具箱具有自扩展功能。
3、减少工厂的层次:由于在.NET中可以使用反射工厂,因此这个优势并不明显。
 
七、缺点
1、在有些情况下克隆功能不容易实现,特别是遇到对象的循环引用时。
 
八、适用性
1、当一个系统应该独立于它的产品创建,构成和表示时;
2、当要实例化的类是在运行时刻指定时,例如,通过动态装载;
3、为了避免创建一个与产品类层次平行的工厂类层次时;
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
 
九、实现要点
1、使用原型管理器,体现在一个系统中原型数目不固定时,可以动态的创建和销毁,如上面的举的调色板的例子。
2、实现克隆操作,在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅表拷贝或通 过序列化的方式来实现深拷贝。
3、Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有稳定的接口。
十、使用场合
当一个系统应该独立于产品的创建、构成和表示时,可以使用原型模式。在原型模式中,产品的创建和初始化在类的Clone方法中完成。在使用时,我们可以用一些列原型对象来代替生成相应对象的工厂对象,并且可以使拷贝、粘贴等操作独立于需要复制的对象。
 
十一、.Net Framework中的应用
.NET中的很多类支持原型模式。例如我们希望获得一个与现有数据集结构相同的数据集,即可采用克隆的方法:
DataSet dsNew=ds.Clone()
注意,与数据相关的类还有一个方法Copy。与克隆不同,该方法不仅复制对象的结构,还复制对象中包含的数据,例如,我们希望复制现有数据集的结构和数据,则采用该方法:
Dsnew=ds.Copy()
使用Clone时,需要注意是深复制还是浅复制。.NET中实现IClone接口的类基本上都采用浅复制的策略,需要深复制的场合需要开发人员根据需要实现。
 
十二、总结
Prototype模式同工厂模式,同样对客户隐藏了对象的创建工作,但是,与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的,达到了“隔离类对象的使用者和具体类型(易变类)之间的耦合关系”的目的。

 
 
参考文献
《敏捷软件开发-原则、模式与实践》 Robert C. Martin
《.Net与设计模式》 甄镭
《Java与模式》 阎宏
“网络资源”