一般情况下,目标类克隆原型中的数据,然后加以改造利用构造自己可以利用的数据。
目标类通过克隆一个或多个原型类来构造,然后再修改或者是填充克隆出来的类的细节以使得其行为符合预先期望。 原型模式一般设计对父类数据的克隆问题。
一般情况下,如果一个类包含一个数组成员,并且这个类实现了ICloneable接口,那么这个类就可以使用Clone()方法来进行克隆,但是克隆是否符合我们的要求就不一定了。
object Clone(); 克隆方法返回的是一个object类。
如果一个包含数组的类实例被克隆,那么新的这个类实例中的数组如果仅仅是原来数组的一个引用,那么就是叫做浅拷贝(shallow copy),到数据对象的引用虽然被复制了,但是他们指向相同的底层数据。在任何副本上执行的操作都会改变原来实例中的数据,有些情况下这并不是我们想要的结果。
所以我们可以进行深拷贝,完全建立一个新的数据实体:
public object Clone() {
//create a new ArrayList
ArrayList swd = new ArrayList ();
//copy in swimmer objects
for(int i=0; i< swdata.Count ; i++)
swd.Add (swdata[i]);
//create new SwimData object with this array
SexSwimData newsd = new SexSwimData (swd);
return newsd;
}
无论何时只要有类可能被创建,或者类在创建之后被修改时,都可以使用原型模式。只要所有的类都有相同的接口,他们就可以进行一些实际上相当不同的操作。
我们有一个Swimmer类:
using System;
using CsharpPats;
namespace DeepSexClone
{
/// <summary>
/// Summary description for Swimmer.
/// </summary>
public class Swimmer:IComparable {
private string name; //name
private string lname, frname;//split names
private int age; //age
private string club; //club initials
private float time; //time achieved
private bool female; //sex
//---------
public Swimmer(string line) {
StringTokenizer tok = new StringTokenizer(line,",");
splitName(tok);
age = Convert.ToInt32 (tok.nextToken());
club = tok.nextToken();
time = Convert.ToSingle (tok.nextToken());
string sx = tok.nextToken().ToUpper ().Trim ();
female = sx.Equals ("F");
}
//---------
private void splitName(StringTokenizer tok) {
name = tok.nextToken();
int i = name.IndexOf (" ");
if (i > 0 ) {
frname = name.Substring (0, i);
lname = name.Substring (i + 1).Trim ();
}
}
//---------
public int CompareTo(object swo) {
Swimmer sw = (Swimmer)swo;
return lname.CompareTo (sw.getLName() );
}
//---------
public bool isFemale() {
return female;
}
//---------
public int getAge() {
return age;
}
//---------
public float getTime() {
return time;
}
//---------
public string getName() {
return name;
}
//---------
public string getClub() {
return club;
}
//---------
public string getLName() {
return lname;
}
}
}
一个包含游泳选手信息的SwimData类:
using System;
using System.Collections ;
using CsharpPats;
namespace DeepSexClone
{
/// <summary>
/// Summary description for SwimData.
/// </summary>
public class SwimData:ICloneable {
protected ArrayList swdata;
private int index;
//-----
public SwimData() {
swdata = new ArrayList ();
}
//-----
public SwimData(ArrayList swd) {
swdata = swd;
index=0;
}
//-----
public int count() {
return swdata.Count ;
}
//-----
public SwimData(string filename) {
swdata = new ArrayList ();
csFile fl = new csFile(filename);
fl.OpenForRead ();
string s = fl.readLine ();
while(s != null) {
Swimmer sw = new Swimmer(s);
swdata.Add (sw);
s = fl.readLine ();
}
fl.close ();
}
//-----
public object Clone() {
//create a new ArrayList
ArrayList swd = new ArrayList ();
//copy in swimmer objects
for(int i=0; i< swdata.Count ; i++)
swd.Add (swdata[i]);
//create new SwimData object with this array
SwimData newsd = new SwimData (swd);
return newsd;
}
//-----
public void moveFirst() {
index = 0;
}
//-----
public bool hasMoreElements() {
return (index < swdata.Count-1 );
}
//-----
public void sort() {
//sort using IComparable interface of Swimmer
swdata.Sort (0, swdata.Count , null);
}
//-----
public Swimmer getSwimmer() {
if(index < swdata.Count )
return (Swimmer)swdata[index++];
else
return null;
}
}
}
我们有一些类继承自SwimData类,这些继承自SwimData类的子类就需要克隆父类中的游泳选手的信息。我们有一个SexSwimData类继承SwimData类,并且这个类有自己的对游泳选手的排序方法,并且SexSwimData类的需要有一个自己的Clone()方法,因为要返回不同的数据类型,所以在这个类中需要重写父类的Clone()方法:
public object Clone() {
//create a new ArrayList
ArrayList swd = new ArrayList ();
//copy in swimmer objects
for(int i=0; i< swdata.Count ; i++)
swd.Add (swdata[i]);
//create new SwimData object with this array
SexSwimData newsd = new SexSwimData (swd);
return newsd;
}
这样,每次有一个新的SwimData类的子类实现的时候,不得不每次都重写父类的Clone()方法,因为每次要返回的数据都不一样,这是一种非常不好的方法。一种更好的实现方案是在每一个有Clone()方法的类中取消ICloneable接口的限制,反转处理过程,让每个接收类都克隆发送类内部的数据。
这样,对SwimData类的改动如下:
public virtual void cloneMe(SwimData swdat) {
swdata = new ArrayList ();
ArrayList swd=swdat.getData ();
//copy in swimmer objects
for(int i=0; i < swd.Count ; i++)
swdata.Add (swd[i]);
}
将Clone()函数取消,取消对ICloneable接口的实现,而是使用上面的函数 cloneMe();
这种方法对于SwimData的所有子类来说都是可用的,因为无需在子类之间进行数据的强制类型转换。所有的子类都拥有同样的数据。
比如下面的例子,根据游泳选手信息画出年龄分布柱状图。
AgeSwimData类继承自SwimData 类:
using System;
using System.Collections ;
namespace DeepSexClone
{
/// <summary>
/// Summary description for SexSwimData.
/// </summary>
public class AgeSwimData:SwimData
{
ArrayList swd;
public AgeSwimData() {
swdata = new ArrayList ();
}
//------
public AgeSwimData(string filename):base(filename){}
public AgeSwimData(ArrayList ssd):base(ssd){}
//------
public override void cloneMe(SwimData swdat) {
swd = swdat.getData ();
}
//------
public override void sort() {
Swimmer[] sws = new Swimmer[swd.Count ];
//copy in swimmer objects
for(int i=0; i < swd.Count ; i++) {
sws[i] = (Swimmer)swd[i];
}
//sort into increasing order
for( int i=0; i< sws.Length ; i++) {
for (int j = i; j< sws.Length ; j++) {
if (sws[i].getAge ()>sws[j].getAge ()) {
Swimmer sw = sws[i];
sws[i]=sws[j];
sws[j]=sw;
}
}
}
int age = sws[0].getAge ();
int agecount = 0;
int k = 0;
swdata = new ArrayList ();
bool quit = false;
while( k < sws.Length && ! quit ) {
while(sws[k].getAge() ==age && ! quit) {
agecount++;
if(k < sws.Length -1)
k++;
else
quit= true;
}
//create a new Swimmer with a series of X's for a name
//for each new age
string name = "";
for(int j = 0; j < agecount; j++)
name +="X";
Swimmer sw = new Swimmer(age.ToString() + " " +
name + "," + age.ToString() + ",club,0,F");
swdata.Add (sw);
agecount = 0;
if(quit)
age = 0;
else
age = sws[k].getAge ();
}
}
}
}
AgeSwimData的cloneMe()方法返回的是对一个传入类的数据的引用类型,并没有进行深拷贝。但是在sort方法中新建了一个数组用来存放一个选手信息的新的拷贝,并对这个选手数组进行排序,没有改动传入数据。
private void btClone_Click(object sender, System.EventArgs e) {
AgeSwimData newSd = new AgeSwimData ();
newSd.cloneMe (swdata);
newSd.sort ();
lsNewKids.Items.Clear() ;
while(newSd.hasMoreElements() ) {
Swimmer sw = (Swimmer)newSd.getSwimmer ();
lsNewKids.Items.Add (sw.getName() );
}
}
这样在主程序中新建了一个AgeSwimData的实例,这个实例在排序的时候使用的并不是原来传入实例的数据。
原型模式最大的好处是,无论返回的是哪一个子类,都不需要把该类转换成新的类型才能在程序中使用,父类级别的抽象类或者基类的方法应该是足够使用的,客户端永远都不需要知道其正在处理的是真正的子类的哪一个类。
当创建新的实例需要更多的代价的时候,原型模式复制或者克隆现有的类而不是创建新的实例。