Dot Net设计模式—原型模式
1.引言
在商品房销售系统中,房屋信息是基础信息。在系统运行前必须输入房屋的各种信息到系统中,这是一项枯燥的重复劳动。如果让用户重复输入房间的类型、面积和卫生间样式,这个系统肯定尚未运行就夭折了。实际上,一个小区楼盘的样式并不多,不同的只是楼号。另外,楼盘中的房间类型也非常有限,从而为解决输入问题提供了启示。楼盘的逻辑结构如图所示。
一个小区包含多个楼盘,一个楼盘包括多层,一层包括多个房间。楼盘和房间都有自己的样式(这个模型忽略高档住宅和别墅),要解决的问题是如何创建这些类的实例,这些实例内部的数据基本相同。
方案一:手工输入,根据录入的数据实例化对象。
这个方案在增加用户痛苦的同时,并未给程序员带来更多的好处,不予考虑。
方案二:由用户输入一些基本信息和规则,由程序来实例化对象。
系统执行过程如下。
(1) 用户选择新增一个楼盘,系统创建楼盘实例。
(2) 用户输入一个房间的基本信息和需要自动生成的楼层,系统实例化这些信息,
然后增加到楼盘中。
(3) 用户重复第2步,直到输入整个楼盘的数据。
这个方案中的用户操作已经减少了很多,但仍有问题,如果一个小区的各个楼盘结构完全一样,那么如何自动生成?为此在用户界面上需要增加生成楼盘组,然后输入需要生成的数量及房间信息等,这样使得系统变得非常不灵活,无法扩充。例如,需要建立一个基本类似的小区。并且也很难去掉某些功能,例如不需要自动生成楼盘组。如果自动生成出现问题,例如房号的规则输入错误,则必须销毁对象后重新处理。虽然系统中已经存在一个现成的楼盘(例如一幢已经销售完成的楼盘与新楼盘结构一样),但是却无法使用。
从程序角度更是难以维护,每新增一级输入,都需要改动创建对象的工厂方法。
不幸的是,这种方案在重复输入信息的系统中经常被采用,从而导致系统变得难以维护。
方案三:拷贝/粘贴。
从用户的角度看,系统最好支持拷贝/粘贴。例如复制一个现成的楼盘,粘贴后只要修改楼号即可,或者复制多个楼层粘贴后自动增加到楼层中,这样用户的交互过程要简单很多。
2.概述
2.1意图
采用原型模式可以解决上节提到的问题我们将已经存在的对象(楼盘、楼层、房间,甚至小区)作为原型,用户可以通过复制这些原型创建新的对象。
2.2使用场合
当一个系统应该独立于产品的创建、构成和表示时,可以使用原型模式。在原型模式中,产品的创建和初始化在类的Clone方法中完成。在使用时,我们可以用一些列原型对象来代替生成相应对象的工厂对象,并且可以使拷贝、粘贴等操作独立于需要复制的对象。
2.3原型模式的结构
原型模式的结构如图所示。
(1) Prototype:声明一个克隆自身的接口。
(2) ConcretePrototype:实现一个克隆自身的操作。
(3) Client:让一个原型克隆自身从而创建一个新的对象。
2.4效果
使用原型模式有以下优点。
(1)在运行时增加或删除产品:只要通过客户原型实例即可将新产品类型增加到系统中,例如组态软件中工具箱中的每个工具可以对应一个注册的原型对象,可以通过增加原形对象扩展工具箱。
(2)很容易地创建复杂的对象:在图形编辑和组态等软件中,经常需要创建复杂的图元。这些图元是由简单图元组成的,采用原型模式可以很容易地将复杂图元作为一般图元来使用,使软件的工具箱具有自扩展功能。
(3)减少工厂的层次:由于在.NET中可以使用反射工厂,因此这个优势并不明显。
使用原型模式的缺点是在有些情况下克隆功能不容易实现,特别是遇到对象的循环引用时。
3..NET实现
3.1Icloneable接口
.NET已经为实现提供了必要的基础,即System命名空间中的ICloneable接口。这个接口只有Clone这一个方法。实现这个接口的类也就具备了Clone方法,实现克隆还需要我们编写代码。
3.2实现结构
假如我们需要一个计算机类,这个类的属性包括品牌、CPU、RAM和硬盘容量等。如果克隆同配置的计算机,并希望这个类可以成为原型,则代码如下:
Public Class Computer
Implements Icloneable
Private m-CPU As String
Private m-Brand As String
Private m-RAM As String
Private m-HardDisk As String
Private m-Number As String
Public Property Number ( ) As String
Public Property CPUr ( ) As String
Public Property Brand ( ) As String
Public Property RAM( ) As String
Public Property HardDisk( ) As String
Public Function Clone ( ) As Object Implements System.Icloneable.Clone
Begin
Dim c As New Computer
c.RAM = m-RAM
c.Brand = m-Brand
c.CPU = m-CPU
c.HardDisk = m-HardDisk
Return c
End Function
End Class
这段代码来源于设备管理系统,并进行了简化。注意在复制代码时并没有复制Number属性,这是因为该属性是用来区别计算机的设备号,不能重复,因此应该由客户程序赋值或者由序号发生器生成。
3.3深复制与浅复制
如果仅仅从示例代码上看,实现原型模式并不困难。然而复杂的情况是,如果一个对象是复合对象或者包含其他的对象,那么克隆对象的问题是仅仅复制对象本身,还是连同被引用的对象一起复制?
例如,我们在克隆一个楼盘时,希望获得关于这个楼盘的所有新的数据,以便于修改。这时在复制楼盘时,复制了楼盘对象及其包含的所有房间对象,并且隶属于新产生的楼盘。在这个操作中,房间的复制是间接进行的,这种情况是深复制。即除了引用其他对象的变量外,被复制对象的所有变量都含有与原对象相同的值,引用其他对象的变量将指向原有这些变量的复制品。
有时我们不希望复制包含的对象,而是需要所复制对象中包含的对象仍然指向原来的引用。例如,克隆用户清单,其中包括了用户对象,而克隆的用户清单不需要复制所包含的用户对象。这样不管修改了哪个清单中的用户信息,都可以在另一个清单中得到反应,这种情况是浅复制。即被复制对象的所有变量都含有与原对象相同的值,换言之,所有引用其他对象的变量仍然指向原来引用的对象。
5..NET中的原型模式
.NET中的很多类支持原型模式。例如我们希望获得一个与现有数据集结构相同的数据集,即可采用克隆的方法:
Dim dsnew As DataSet=ds.Clone
注意,与数据相关的类还有一个方法Copy。与克隆不同,该方法不仅复制对象的结构,还复制对象中包含的数据,例如,我们希望复制现有数据集的结构和数据,则采用该方法:
Dsnew=ds.Copy
使用Clone时,需要注意是深复制还是浅复制。.NET中实现IClone接口的类基本上都采用浅复制的策略,需要深复制的场合需要开发人员根据需要实现。
在商品房销售系统中,房屋信息是基础信息。在系统运行前必须输入房屋的各种信息到系统中,这是一项枯燥的重复劳动。如果让用户重复输入房间的类型、面积和卫生间样式,这个系统肯定尚未运行就夭折了。实际上,一个小区楼盘的样式并不多,不同的只是楼号。另外,楼盘中的房间类型也非常有限,从而为解决输入问题提供了启示。楼盘的逻辑结构如图所示。
一个小区包含多个楼盘,一个楼盘包括多层,一层包括多个房间。楼盘和房间都有自己的样式(这个模型忽略高档住宅和别墅),要解决的问题是如何创建这些类的实例,这些实例内部的数据基本相同。
方案一:手工输入,根据录入的数据实例化对象。
这个方案在增加用户痛苦的同时,并未给程序员带来更多的好处,不予考虑。
方案二:由用户输入一些基本信息和规则,由程序来实例化对象。
系统执行过程如下。
(1) 用户选择新增一个楼盘,系统创建楼盘实例。
(2) 用户输入一个房间的基本信息和需要自动生成的楼层,系统实例化这些信息,
然后增加到楼盘中。
(3) 用户重复第2步,直到输入整个楼盘的数据。
这个方案中的用户操作已经减少了很多,但仍有问题,如果一个小区的各个楼盘结构完全一样,那么如何自动生成?为此在用户界面上需要增加生成楼盘组,然后输入需要生成的数量及房间信息等,这样使得系统变得非常不灵活,无法扩充。例如,需要建立一个基本类似的小区。并且也很难去掉某些功能,例如不需要自动生成楼盘组。如果自动生成出现问题,例如房号的规则输入错误,则必须销毁对象后重新处理。虽然系统中已经存在一个现成的楼盘(例如一幢已经销售完成的楼盘与新楼盘结构一样),但是却无法使用。
从程序角度更是难以维护,每新增一级输入,都需要改动创建对象的工厂方法。
不幸的是,这种方案在重复输入信息的系统中经常被采用,从而导致系统变得难以维护。
方案三:拷贝/粘贴。
从用户的角度看,系统最好支持拷贝/粘贴。例如复制一个现成的楼盘,粘贴后只要修改楼号即可,或者复制多个楼层粘贴后自动增加到楼层中,这样用户的交互过程要简单很多。
2.概述
2.1意图
采用原型模式可以解决上节提到的问题我们将已经存在的对象(楼盘、楼层、房间,甚至小区)作为原型,用户可以通过复制这些原型创建新的对象。
2.2使用场合
当一个系统应该独立于产品的创建、构成和表示时,可以使用原型模式。在原型模式中,产品的创建和初始化在类的Clone方法中完成。在使用时,我们可以用一些列原型对象来代替生成相应对象的工厂对象,并且可以使拷贝、粘贴等操作独立于需要复制的对象。
2.3原型模式的结构
原型模式的结构如图所示。
(1) Prototype:声明一个克隆自身的接口。
(2) ConcretePrototype:实现一个克隆自身的操作。
(3) Client:让一个原型克隆自身从而创建一个新的对象。
2.4效果
使用原型模式有以下优点。
(1)在运行时增加或删除产品:只要通过客户原型实例即可将新产品类型增加到系统中,例如组态软件中工具箱中的每个工具可以对应一个注册的原型对象,可以通过增加原形对象扩展工具箱。
(2)很容易地创建复杂的对象:在图形编辑和组态等软件中,经常需要创建复杂的图元。这些图元是由简单图元组成的,采用原型模式可以很容易地将复杂图元作为一般图元来使用,使软件的工具箱具有自扩展功能。
(3)减少工厂的层次:由于在.NET中可以使用反射工厂,因此这个优势并不明显。
使用原型模式的缺点是在有些情况下克隆功能不容易实现,特别是遇到对象的循环引用时。
3..NET实现
3.1Icloneable接口
.NET已经为实现提供了必要的基础,即System命名空间中的ICloneable接口。这个接口只有Clone这一个方法。实现这个接口的类也就具备了Clone方法,实现克隆还需要我们编写代码。
3.2实现结构
假如我们需要一个计算机类,这个类的属性包括品牌、CPU、RAM和硬盘容量等。如果克隆同配置的计算机,并希望这个类可以成为原型,则代码如下:
Public Class Computer
Implements Icloneable
Private m-CPU As String
Private m-Brand As String
Private m-RAM As String
Private m-HardDisk As String
Private m-Number As String
Public Property Number ( ) As String
Public Property CPUr ( ) As String
Public Property Brand ( ) As String
Public Property RAM( ) As String
Public Property HardDisk( ) As String
Public Function Clone ( ) As Object Implements System.Icloneable.Clone
Begin
Dim c As New Computer
c.RAM = m-RAM
c.Brand = m-Brand
c.CPU = m-CPU
c.HardDisk = m-HardDisk
Return c
End Function
End Class
这段代码来源于设备管理系统,并进行了简化。注意在复制代码时并没有复制Number属性,这是因为该属性是用来区别计算机的设备号,不能重复,因此应该由客户程序赋值或者由序号发生器生成。
3.3深复制与浅复制
如果仅仅从示例代码上看,实现原型模式并不困难。然而复杂的情况是,如果一个对象是复合对象或者包含其他的对象,那么克隆对象的问题是仅仅复制对象本身,还是连同被引用的对象一起复制?
例如,我们在克隆一个楼盘时,希望获得关于这个楼盘的所有新的数据,以便于修改。这时在复制楼盘时,复制了楼盘对象及其包含的所有房间对象,并且隶属于新产生的楼盘。在这个操作中,房间的复制是间接进行的,这种情况是深复制。即除了引用其他对象的变量外,被复制对象的所有变量都含有与原对象相同的值,引用其他对象的变量将指向原有这些变量的复制品。
有时我们不希望复制包含的对象,而是需要所复制对象中包含的对象仍然指向原来的引用。例如,克隆用户清单,其中包括了用户对象,而克隆的用户清单不需要复制所包含的用户对象。这样不管修改了哪个清单中的用户信息,都可以在另一个清单中得到反应,这种情况是浅复制。即被复制对象的所有变量都含有与原对象相同的值,换言之,所有引用其他对象的变量仍然指向原来引用的对象。
5..NET中的原型模式
.NET中的很多类支持原型模式。例如我们希望获得一个与现有数据集结构相同的数据集,即可采用克隆的方法:
Dim dsnew As DataSet=ds.Clone
注意,与数据相关的类还有一个方法Copy。与克隆不同,该方法不仅复制对象的结构,还复制对象中包含的数据,例如,我们希望复制现有数据集的结构和数据,则采用该方法:
Dsnew=ds.Copy
使用Clone时,需要注意是深复制还是浅复制。.NET中实现IClone接口的类基本上都采用浅复制的策略,需要深复制的场合需要开发人员根据需要实现。