代码改变世界

【设计模式】原型模式 Pototype Pattern

2018-08-06 08:45 蓝之风 阅读(...) 评论(...) 编辑 收藏

前面讲了创建一个对象实例的方法单例模式Singleton Pattern, 创造多个产品的工厂模式(简单工厂模式 Simple Factory Pattern, 工厂方法模式 FactoryMothed Pattern抽象工厂模式 Abstract Factory Method),以及创建复杂对象的建造者模式 Builder Pattern, 这几几乎包含了产品创建的各个方方面,但是还有一种,那就是有自我创建能力的模式,这种模式能够创建出和自己相同或者相似的对象。生活中经常也会见到这方面的例子,比如蠕虫病毒的自我复制,细胞分裂以及自我繁殖,游戏角色的自我复制和分身等。

在软件开发中也会经常遇到这样的问题,最近在做项目的时候就碰到了这么一个需求,问卷调查试卷复用的问题。我们的系统用户组成是这样的,系统有一个超级管理员的角色,超级管理员可以干任何事情,系统中还接入了N多公司,每个公司有公司的管理员,公司管理员可以干超级管理员分配给该公司的相应权限, 那么这个问卷调查的需求是这样的:

1. 超级管理员可以创建问卷调查试卷,但是这个问卷调查的试卷不能直接使用,只能供各公司的管理员作为模板样例创建自己公司的问卷调查试卷。

2.各个公司的管理员可以创建自己公司的问卷调查试卷,仅供自己公司员工使用。

3.各个公司的管理员可以可以基于超级管理员创建的问卷调查试卷创建自己的模板,创建出来的问卷调查试卷仅供自己公司的员工使用。

4.公司管理员不能修改超级管理员创建的调查问卷试卷。

5. 超级管理员可以修改自己的问卷调查试卷, 并且不会影响各个公司之前根据问卷调查试卷创建出来的试卷。

需求用文字描述出来有点费劲,看下面这张图:

image

该怎么来实现呢? 这就是本文要讨论的主角原型模式Pototype Pattern ,也是最后一个创建型模式,原型模式就是为解决这类问题而生的:)。

一、原型模式的定义

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

二、原型模式的结构

image 1、Prototype(抽象原型类):

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

2、ConcretePrototype(具体原型类):

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

3、Client(客户类):

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

三、原型模式的经典实现

原型模式的核心是克隆方法的实现:

1、通用实现方法

通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同:

public abstract class Pototype
{
    public string Some { get; set; }
    public abstract Pototype Clone();
}

public class ConcretePototype : Pototype
{
    public override Pototype Clone()
    {
        Pototype pototype = new ConcretePototype();
        pototype.Some = this.Some;

        return pototype;
    }
}

客户端调用:

static void Main(string[] args)
{
    Structure.Pototype pototype = new ConcretePototype();
    pototype.Some = "pototype";
    Structure.Pototype clone = pototype.Clone();
    Console.WriteLine("I'm old "+pototype.Some);
    Console.WriteLine("I'm clone "+clone.Some);

    Console.ReadKey();

}

输出结果:

image

2、C#实现

C# 中有一个实现拷贝的方法MemberwiseClone,在克隆方法中我们用MemberwiseClone来实现克隆一个对象:

public class CsharpPototype : Pototype
{
    public override Pototype Clone()
    {
       return this.MemberwiseClone() as Pototype;       
    }
}

客户端调用:

static void Main(string[] args)
{
    Structure.Pototype pototype = new CsharpPototype();
    pototype.Some = "pototype";
    Structure.Pototype clone = pototype.Clone();
    Console.WriteLine("I'm old "+pototype.Some);
    Console.WriteLine("I'm clone "+clone.Some);

    Console.ReadKey();
}

客户端输出和上面一样, 这种方式实现的是浅拷贝。

深拷贝和浅拷贝, 浅拷贝如果原型对象的成员是值类型那么就将原型对象复制一份给克隆对象,如果是引用类型,只将原型对象的地址复制一份给克隆对象,这时克隆对象和原型对象在内存中指向同一个对象,修改其中的一个会影响另一个。深拷贝,则是将原型对象拷贝一份给克隆对象,克隆对象和原型对象在内存中有独立的存储空间,一个改动了不会影响另一个。

四、原型模式实例

现在我们弄明白了原型模式,现在就来实现开头提出的问卷调查试卷创建的需求,因为试卷的内容比较复杂这里我们是找出一些核心的能够说明问题的模型来演示这个例子。我们先将试卷的内容假定为字符串类型。

这里我们将超级管理员创建的试卷命名为SuperSurveyPaper。

image image

这里SuperSurveyPaper 充当抽象原型类,CompanyASurveyPaper 和CompanyBSurveyPaper 充当具体原型类。

1、浅拷贝实现:

public abstract class SuperSurveyPaper
{
    public string Name { get; set; }
    public string Content { get; set; }
    public abstract SuperSurveyPaper Clone();
}
public class CompanyASurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        return this.MemberwiseClone() as SuperSurveyPaper;
    }
}

public class CompanyBSurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        return this.MemberwiseClone() as SuperSurveyPaper;
    }
}

客户端调用代码:

static void Main(string[] args)
{

    PototypeInstance.SuperSurveyPaper pototype = new CompanyASurveyPaper();
    pototype.Name = "SuperSurveyPaper ->Name";
    pototype.Content = "SuperSurveyPaper -> Content";
    PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
    Console.WriteLine("I'm old Name: " + pototype.Name);
    Console.WriteLine("I'm old Content: " + pototype.Content);
    Console.WriteLine("======================== ===========");
    Console.WriteLine("I'm Clone Name: " + clone.Name);
    Console.WriteLine("I'm Clone Content: " + clone.Content);

    Console.ReadKey();
}

输出结果:

image

这里也可以加入配置通过反射来创建原型对象

在app.config 中加入配置:

<appSettings>
  <add key="Pototype" value="DesignPattern.Pototype.PototypeInstance.CompanyBSurveyPaper"/>
</appSettings>

调用段代码改成:

static void Main(string[] args)
{
    PototypeInstance.SuperSurveyPaper pototype; 
  
    var setting = ConfigurationSettings.AppSettings["Pototype"];
    var obj = Type.GetType(setting);
    if (obj == null) return;
    pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper;

    if (pototype == null) return;

    pototype.Name = "SuperSurveyPaper ->Name";
    pototype.Content = "SuperSurveyPaper -> Content";
    PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
    Console.WriteLine("I'm old Name: " + pototype.Name);
    Console.WriteLine("I'm old Content: " + pototype.Content);
    Console.WriteLine("======================== ===========");
    Console.WriteLine("I'm Clone Name: " + clone.Name);
    Console.WriteLine("I'm Clone Content: " + clone.Content);

    Console.ReadKey();
}

输出结果和上面一样

2、深拷贝

如果现在的试卷内容不是一个简单的字符串了而是一个对象:

[Serializable]
public class SurveyPaperModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
[Serializable]
public abstract class SuperSurveyPaper
{
    public string Name { get; set; }
    public SurveyPaperModel Content { get; set; }
    public abstract SuperSurveyPaper Clone();
}
[Serializable]
public class CompanyASurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, this);
        memoryStream.Position = 0;
        return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
    }
}
[Serializable]
public class CompanyBSurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, this);
        memoryStream.Position = 0;
        return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
    }
}

客户端调用:

static void Main(string[] args)
{
    PototypeInstance.SuperSurveyPaper pototype; 
  
    var setting = ConfigurationSettings.AppSettings["Pototype"];
    var obj = Type.GetType(setting);
    if (obj == null) return;
    pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper;

    if (pototype == null) return;

    pototype.Name = "SuperSurveyPaper ->Name";
    pototype.Content = new SurveyPaperModel { FirstName = "Design", LastName = "Pattern" };
    PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
    Console.WriteLine("I'm old Name: " + pototype.Name);
    Console.WriteLine("I'm old Content: " + pototype.Content.FirstName);
    Console.WriteLine("I'm old Content: " + pototype.Content.LastName);
    Console.WriteLine("======================== ===========");
    Console.WriteLine("I'm Clone Name: " + clone.Name);
    Console.WriteLine("I'm Clone Content: " + clone.Content.FirstName);
    Console.WriteLine("I'm Clone Content: " + clone.Content.LastName);
    Console.WriteLine("pototype==clone:" + clone.Equals(pototype));

    Console.ReadKey();
}

输出结果:

image

 

五、原型模式的优点

  1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  2. 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
  3. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
  4. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

六、原型模式的缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

七、原型模式的使用场景

  1. 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  2. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便

八、扩展-原型管理器

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

还是应用上面的例子,现在加入B公司:

[Serializable]
public class SurveyPaperModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
[Serializable]
public abstract class SuperSurveyPaper
{
    public string Name { get; set; }
    public SurveyPaperModel Content { get; set; }
    public abstract SuperSurveyPaper Clone();
}
[Serializable]
public class CompanyASurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, this);
        memoryStream.Position = 0;
        return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
    }
}
[Serializable]
public class CompanyBSurveyPaper : SuperSurveyPaper
{
    public override SuperSurveyPaper Clone()
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, this);
        memoryStream.Position = 0;
        return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
    }
}

public class PototypeManager
{
    static IDictionary<string, SuperSurveyPaper> superSurveyPapers = new Dictionary<string, SuperSurveyPaper>();
    static PototypeManager()
    {
        SuperSurveyPaper companyASurveyPaper = new CompanyASurveyPaper();
        companyASurveyPaper.Name = "Company A";
        companyASurveyPaper.Content= new SurveyPaperModel {FirstName="Michael",LastName="Du"};

        SuperSurveyPaper companyBSurveyPaper = new CompanyBSurveyPaper();
        companyBSurveyPaper.Name = "Company B";
        companyBSurveyPaper.Content = new SurveyPaperModel { FirstName = "Kevin", LastName = "Durant" };

        superSurveyPapers.Add("CompanyA", companyASurveyPaper);
        superSurveyPapers.Add("CompanyB", companyBSurveyPaper);
    }
    private PototypeManager (){}
    public SuperSurveyPaper GetSuperPaper(string key){
        return superSurveyPapers[key].Clone();
    }
    public void RegisterSurveyPaper(string key, SuperSurveyPaper ssp){
        superSurveyPapers.Add(key, ssp);
    }
    public static PototypeManager Instance
    {
        get{ return PototypeManagerInitializer.instance;}
    }
    private static class  PototypeManagerInitializer
    {
        public static readonly PototypeManager instance=new PototypeManager();
    }
}

这里的PototypeManager类使用了一个Singleton模式创建出来,在静态构造里初始化了原型对象,并将其注册在一个字典中,这个在项目中数据是从数据库中直接读取的。这个管理类还暴露了一个注册原型实例的方法,便于扩展和动态给管理器增加原型对象。在获取Clone对象的方法中直接将原型对象的一个Copy返回给客户程序。确保客户端得到的对象是一个全新的对象。

客户端调用代码:

static void Main(string[] args)
{
    PototypeInstance.SuperSurveyPaper pototype1, pototype2, pototype3, pototype4;

    pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyA");
    pototype2 = PototypeManager.Instance.GetSuperPaper("CompanyA");
    Console.WriteLine("I'm old Name: " + pototype1.Name);
    Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName);
    Console.WriteLine("I'm old Content: " + pototype1.Content.LastName);

    Console.WriteLine("I'm Clone Name: " + pototype2.Name);
    Console.WriteLine("I'm Clone Content: " + pototype2.Content.FirstName);
    Console.WriteLine("I'm Clone Content: " + pototype2.Content.LastName);
    Console.WriteLine("pototype1==pototype2:" + pototype2.Equals(pototype1));
    Console.WriteLine("======================== ===========");
    pototype3 = PototypeManager.Instance.GetSuperPaper("CompanyB");
    pototype4 = PototypeManager.Instance.GetSuperPaper("CompanyB");
    Console.WriteLine("I'm old Name: " + pototype3.Name);
    Console.WriteLine("I'm old Content: " + pototype3.Content.FirstName);
    Console.WriteLine("I'm old Content: " + pototype3.Content.LastName);

    Console.WriteLine("I'm Clone Name: " + pototype4.Name);
    Console.WriteLine("I'm Clone Content: " + pototype4.Content.FirstName);
    Console.WriteLine("I'm Clone Content: " + pototype4.Content.LastName);
    Console.WriteLine("pototype3==pototype4:" + pototype4.Equals(pototype3));
    Console.ReadKey();
}

输出结果:

image

模拟ctrl+c,ctrl+v

使用原型模式的“自我”复制能力,我们可以很容易的实现,创建副本和撤销副本的功能, 在控制台中我们输入c  替代ctrl+c,输入:z 替代ctrl+z  来模拟这个拷贝和撤销的过程,首先我们创建一个原型对象,每次按C的时候使用最后clone出来的对象再克隆新的对象,并把这些对象依次保存在一个list中,当按Z的时候我们依次在list中移除最后加入的对象直到起初创建的原型对象为止,  简单的客户端代码实现如下:

static List<PototypeInstance.SuperSurveyPaper> _list = new List<PototypeInstance.SuperSurveyPaper>();
static List<SurveyPaperModel> _listModel = new List<SurveyPaperModel>
{
    new SurveyPaperModel{FirstName="Terry",LastName="Go"},
    new SurveyPaperModel{FirstName="Ke",LastName="Be"},
    new SurveyPaperModel{FirstName="Lebron",LastName="Jimes"},
    new SurveyPaperModel{FirstName="Steve",LastName="Jo"},
    new SurveyPaperModel{FirstName="Stive",LastName="Kurry"},
    new SurveyPaperModel{FirstName="Henry",LastName="He"},
    new SurveyPaperModel{FirstName="Kevin",LastName="Druant"},
    new SurveyPaperModel{FirstName="Blue",LastName="Jhon"},
    new SurveyPaperModel{FirstName="Jerry",LastName="Ma"},
    new SurveyPaperModel{FirstName="Fred",LastName="Gao"},
};
static void Main(string[] args)
{
    PototypeInstance.SuperSurveyPaper pototype1;

    pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyB");
    Console.WriteLine("I'm old Name: " + pototype1.Name);
    Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName);
    Console.WriteLine("I'm old Content: " + pototype1.Content.LastName);
    _list.Add(pototype1);
    while (true)
    {
        var key = Console.ReadKey();

        switch (key.Key)
        {
            case ConsoleKey.C:
                var pototypeLastInstance = _list.Last<PototypeInstance.SuperSurveyPaper>();
                var cloneFromPototypeLastInstance = pototypeLastInstance.Clone();
                cloneFromPototypeLastInstance.Name = "Version " + _list.Count;
                Random rd = new Random();
                cloneFromPototypeLastInstance.Content = _listModel[rd.Next(0, _listModel.Count)];
                _list.Add(cloneFromPototypeLastInstance);
                PrintList(_list);
                break;
            case ConsoleKey.Z:
                if (_list.Count > 1)
                    _list.RemoveAt(_list.Count - 1);
                PrintList(_list);
                break;
            case ConsoleKey.Q:
                return;
        }
    }
    Console.ReadKey();
}
static void PrintList(List<PototypeInstance.SuperSurveyPaper> list)
{
    Console.WriteLine("=========");
    var pototpe = list.Last();
    Console.WriteLine("History:" + pototpe.Name);
    Console.WriteLine("I'm Firstname: " + pototpe.Content.FirstName);
    Console.WriteLine("I'm LastName: " + pototpe.Content.LastName);
}

客户端输出:

image

好了,到这里设计模式的创建型模式就全部讨论完了。下面接着讨论结构型模式。