我也设计模式——5.Prototype

我很喜欢这个模式,因为它解决了对象赋值的问题。尤其是那个Clone方法,记忆犹新。

使用场景:当一个系统应该独立于产品的创建,构成和表示时,使用设计模式。


.NET提供了ICloneable接口,只要实现了其Clone方法,在其中执行复制对象的操作。关键就是这个Clone方法,由于对象的复杂性,分为浅复制
和深复制两种:
对于浅复制,可以使用Object的MembermiseClone方法:
    public class A : ICloneable
    
{
        
public object Clone()
        
{
            
return this.MemberwiseClone();
        }

    }
但是,这样复制的对象没有任何区别。于是,可以使用如下折中的办法,
    class Program
    
{
        
static void Main()
        
{
            A a1 
= new A("bjq");

            A a2 
= (A)a1.Clone();
            a2.f1 
= "jax";
        }

    }


    
public class A : ICloneable
    
{
        
public string f1;
        
public string f2;

        
public A(string f1)
        
{
            
this.f1 = f1;
        }


        
public object Clone()
        
{
            A a 
= new A();
            a.f2 
= f2;

            
return a;
        }

    }
可以看到,对象a2虽然是a1的复制,但f1字段是不一样的。
——这里说的不一样,不是具有相同字段/属性值的两个不同指针,我只是想找出两个从外表看有区别的对象。

对于深复制,要使用序列化来实现Clone方法,从而不仅仅复制对象本身,同时连同被引用的对象一起复制,代码如下:
    [Serializable]
    
public class A
    
{
        
private string f1;
        
private string f2;

        
public A(string f1, string f2)
        
{
            
this.f1 = f1;
            
this.f2 = f2;
        }


        
public A Clone()
        
{
            MemoryStream memoryStream 
= new MemoryStream();
            BinaryFormatter formatter 
= new BinaryFormatter();
            formatter.Serialize(memoryStream, 
this);
            memoryStream.Position 
= 0;
            A a 
= (A)formatter.Deserialize(memoryStream);

            
return a;
        }

    }

这里只是说明如何正确使用串行化,而效果并不明显,如果引用一个数组,则能表现出深浅序列化的区别。
深复制在互相引用时,会陷入死循环:A引用B,B又引用A——对于此,是没有完美的解决方案的,为此,我们可以加一个didClone标记,以减少这种问题。

案例分析:
IDE中的ToolBox中各个小工具的实现,比如说Button和TextBox等,可以用原型模式实现。每个Control都要实现Clone()方法,比如说Label:
    public class myLabel : Label, ICloneable
    
{
        
public object Clone()
        
{
            myLabel ml 
= new myLabel();
            
return ml;
        }

    }
于是,在Form中实现如下:
    public partial class Form1 : Form
    
{
        
private Hashtable ht = new Hashtable();
        
private string status = "Label";

        
private void Form1_Load(object sender, EventArgs e)
        
{
            MyLabel ml 
= new MyLabel();
            ml.Text 
= "Hello";
            ht.Add(
"Label", ml);
        }


        
private void button1_Click(object sender, EventArgs e)
        
{
            ICloneable fc 
= (ICloneable)ht[status];
            Control c 
= (Control)fc.Clone();
            
this.Controls.Add(c);
        }

    }
我们用 “点击button1按钮” 来模拟 “从ToolBox中拖动Control到Form” 这个功能。这里仅仅实现了Label,我们还可以用同样的方法模拟Button,TreeView等控件。

案例2,调色板,这个案例参见http://terrylee.cnblogs.com/archive/2006/01/16/317896.html,其思想与案例1是一样的:每次得到的色笔都是一个copy,仅仅是颜色的不同。由于每种颜色由RGB三原色组合搭配而成,所以会有256^3个对象,为此,使用原型模式可以将这个对象减少为1个,可以看成是水彩画中的一支毛笔和RGB三个色盒。

原型模式可以解决抽象工厂平行子类太多的问题,同样的,Flyweight也可以实现,只是后者更倾向于结构而不关心生成,这时候要使用注册工厂。

对于DataSet ds:
    ds.Copy()方法,返回ds的结构和数据;
    ds.Clone()方法,返回ds的结构

原型模式的缺点:
Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。

关于深浅复制的定义:
浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么两个对象将引用同一个字符串。而深拷贝是对对象实例中字段引用的对象也进行拷贝的一种方式,所以如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个深拷贝的话,我们将创建一个新的对象和一个新的字符串--新对象将引用新字符串。需要注意的是执行深拷贝后,原来的对象和新创建的对象不会共享任何东西;改变一个对象对另外一个对象没有任何影响。
    ——以上摘自CLR框架设计,Array是最好的例证。
posted @ 2007-04-16 23:17  包建强  Views(794)  Comments(0Edit  收藏  举报