原型模式

一、概述

原型模式是一种特殊的创建型模式(对象创建型模式),它通过复制一个已有对象来获取更多相同或者相似的对象。原型模式可以提高相同类型对象的创建效率,简化创建过程。其中原型模式又分为浅拷贝和深拷贝。浅拷贝是指复制引用但不复制引用的对象,而深拷贝复制的引用和引用对象。

定义:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。

二、模式结构

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

  • ConcretePrototype(具体原型类):它实现抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

  • Client类:让一个原型对象克隆自身从而创建一个新的对象。

    image-20240617231811026

三、具体实现

以简历为例,复制多份简历,使用原型模式来实现,由于C#中提供了ICloneable接口(抽象原型类),所有具体原型类实现IClonable接口。

3.1 浅拷贝

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象, 某个对象修改,所有的对象都会随之一起修改。

  • Resume(具体原型类):简历类
public class WorkExperience
{
    private string _workDate;
    public string WorkDate { 
        get 
        { 
            return _workDate; 
        }
        set
        {
            _workDate = value;
        }
    }
    private string _company;
    public string Company
    {
        get
        {
            return _company;
        }
        set
        {
            _company = value;
        }
    }
}

public class Resume : ICloneable
{
    private string _name;
    private string _sex;
    private string _age;
    private WorkExperience workExperience;

    public Resume(string name)
    {
        _name = name;
        workExperience = new WorkExperience();
    }

    // 设置个人信息
    public void SetPersonalInfo(string sex, string age)
    {
        _sex = sex;
        _age = age;
    }
    // 设置工作经历
    public void SetWorkExperience(string workData, string company)
    {
        workExperience.WorkDate = workData;
        workExperience.Company = company;
    }
    // 显示
    public void Display()
    {
        Console.WriteLine("{0} {1} {2}", _name, _sex, _age);
        Console.WriteLine("工作经历: {0} {1}", workExperience.WorkDate, workExperience.Company);
    }

    public object Clone()
    {
        return (Object)MemberwiseClone();
    }
}
  • Client类

    Resume resume = new Resume("lwy");
    resume.SetPersonalInfo("男", "23");
    resume.SetWorkExperience("2023-2024", "xx公司");
    
    Resume resume1 = (Resume)resume.Clone();
    resume1.SetWorkExperience("2022-2023", "yy企业");
    
    Resume resume2 = (Resume)resume.Clone();
    resume2.SetPersonalInfo("男", "24");
    
    resume.Display();
    resume1.Display();
    resume2.Display();
    
    /*
    lwy 男 23
    工作经历: 2022-2023 yy企业
    lwy 男 23
    工作经历: 2022-2023 yy企业
    lwy 男 24
    工作经历: 2022-2023 yy企业
    */

string虽然是一种引用类型,但它属于拥有值类型特点的特殊应用类型, MemberwiseClone()方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象

3.2 深拷贝

深拷贝:深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象,每个对象都独立存在。

  • Resume(具体原型类):简历类
public class WorkExperience : ICloneable
{
    private string _workDate;
    public string WorkDate { 
        get 
        { 
            return _workDate; 
        }
        set
        {
            _workDate = value;
        }
    }
    private string _company;
    public string Company
    {
        get
        {
            return _company;
        }
        set
        {
            _company = value;
        }
    }

    public object Clone()
    {
        return (Object) this.MemberwiseClone();
    }
}

public class Resume : ICloneable
{
    private string _name;
    private string _sex;
    private string _age;
    private WorkExperience workExperience;

    public Resume(string name)
    {
        _name = name;
        workExperience = new WorkExperience();
    }

    // 设置个人信息
    public void SetPersonalInfo(string sex, string age)
    {
        _sex = sex;
        _age = age;
    }
    // 设置工作经历
    public void SetWorkExperience(string workData, string company)
    {
        workExperience.WorkDate = workData;
        workExperience.Company = company;
    }
    // 显示
    public void Display()
    {
        Console.WriteLine("{0} {1} {2}", _name, _sex, _age);
        Console.WriteLine("工作经历: {0} {1}", workExperience.WorkDate, workExperience.Company);
    }

    public Object Clone()
    {
        Resume obj = new Resume(_name);
        obj.workExperience = (WorkExperience)workExperience.Clone();
        obj._sex = _sex;
        obj._age = _age;
        return obj;
    }
}
  • Client类

    Resume resume = new Resume("lwy");
    resume.SetPersonalInfo("男", "23");
    resume.SetWorkExperience("2023-2024", "xx公司");
    
    Resume resume1 = (Resume)resume.Clone();
    resume1.SetWorkExperience("2022-2023", "yy企业");
    
    Resume resume2 = (Resume)resume.Clone();
    resume2.SetPersonalInfo("男", "24");
    
    resume.Display();
    resume1.Display();
    resume2.Display();
    
    /*
    lwy 男 23
    工作经历: 2023-2024 xx公司
    lwy 男 23
    工作经历: 2022-2023 yy企业
    lwy 男 24
    工作经历: 2023-2024 xx公司
    */

四、优缺点

  • 优点:
    1. 性能高:使用原型模式复用现有对象,比使用构造函数重新创建对象性能更高(直接在内存中拷贝,构造函数是不会执行的),适用于类实例对象开销较大的情况。
    2. 流程简单:原型模式可以简化创建过程,直接修改现有对象实例的值,达到复用的目的。
  • 缺点:
    1. 实现复杂:需要覆盖clone方法,且需注意深拷贝和浅拷贝的风险,实现深拷贝需要将每一层的对象的类都支持深拷贝。
    2. 不符合开闭原则:因为每一个类配备一个克隆的方法,且该克隆方法位于类内部,当对已有的类进行修改时需要修改源代码。

五、适用环境

  1. 当需要在运行是动态地创建新对象,且不必知道它们确切类型时,可以采用原型模式。
  2. 当类初始化时消耗过多资源,或者构造函数复杂,或者需要避免创建新对象时所需的构造函数调用开销,都可以考虑使用原型模式。