设计模式(七)原型模式

要求有一个简历,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要写三份简历。

 

初步实现

简历类:

// 简历类
class Resume {
    private string name;
    private string sex;
    private string age;
    private string timeArea;
    private string company;

    public Resume(string name){
        this.name=name;
    }

    // 设置个人信息
    public void SetPersonalInfo(string sex, string age){
        this.sex=sex;
        this.age=age;
    }

    // 设置工作经历
    public void SetWorkExperience(string timeArea,string company){
        this.timeArea=timeArea;
        this.company=company;
    }

    // 显示 
    public void Display(){
        Console.WriteLine("{0} {1} {2}", name, sex, age);
        Console.WriteLine("工作经历:{0} {1}", timeArea, company);
    }
}

客户端调用代码:

static void Main(string[] args){
    Resume a= new Resume("大鸟");
    a.SetPersonalInfo("男","29");
    a.SetWorkExperience("1998-2000","XX公司");

    Resume b= new Resume("大鸟");
    b.SetPersonalInfo("男","29");
    b.SetWorkExperience("1998-2000","XX公司");

    Resume c= new Resume("大鸟");
    c.SetPersonalInfo("男","29");
    c.SetWorkExperience("1998-2000","XX公司");

    a.Display(); 
    b.Display(); 
    c.Display();

    Console.Read();    
}

结果显示:

考虑:三份简历需要三次实例化,这样的客户端是不是很麻烦?如果要二十份,就需要二十次实例化。不仅如此,如果写错了一个字,比如98年改成99年,那就要改二十次!

其实,可以这样写——

static void Main(string[] args){
    Resume a= new Resume("大鸟");
    a.SetPersonalInfo("男","29");
    a.SetWorkExperience("1998-2000","XX公司");
    
    Resume b=a;
    Resume c=a;
    
    a.Display();
    b.Display();
    c.Display();
    
    Console.Read();
}

这其实是传引用,而不是传值。这样做就如同是在b纸张和c纸张上写着简历在a处一样,没有实际内容。

 

原型模式

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

原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节

来看代码——

原型类:

abstract class Prototype{
    private string id;
    
    public Prototype(string id){
        this.id=id;
    }

    public string Id{
        get{return id;}
    }

    // 抽象类关键就是有这样一个Clone方法
    public abstract Prototype Clone();
}

具体原型类:

class ConcretePrototype1 : Prototype{
    public ConcretePrototype1(string id) :base(id){}

    public override Prototype Clone(){
        // 创建当前对象的浅表副本,方法是创建一个新对象,
        // 然后将当前对象的非静态字段复制到该新对象,
        // 如果字段是值类型的,则对该字段执行逐位复制;
        // 如果字段是引用类型,则复制引用但不复制引用的对象
        // 因此,原始对象及其副本引用同一对象

        return (Prototype)this.MeberwiseClone();
    }
}

客户端代码:

static void Main(string[] args){
    ConcretePrototype1 p1 = new ConcretePrototype1("I");

    // 克隆类ConcretePrototype1的对象p1就能得到新的实例c1
    ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone();
    Console.WriteLine("Cloned: {0}".c1.Id);

    Console.Read();
}

.NET在System命名空间中提供了ICloneable接口,其中就是唯一的一个方法Clone(),这样就只需要实现这个接口就可以完成原型模式了。

 

第二版

代码结构图:

简历类:

// 简历
class Resume : ICloneable{
    private string name;
    private string sex;
    private string age;
    private string timeArea;
    private company;

    public Resume(string name){
        this.name=name;
    }

    // 设置个人信息
    public void SetPersonalInfo(string sex, string age){
        this.sex=sex;
        this.age=age;
    }

    // 设置工作经历
    public void SetWorkExperience(string timeArea, string company){
        this.timeArea = timeArea;
        this.company=company;
    }
    
    // 显示
    public void Display(){
        Console.WriteLine("{0}{1}{2}",name,sex,age);
        Console.WriteLine("工作经历:{0}{1}",timaArea,company);    // 实现接口的方法,用来克隆对象
    }
    
    public Object Clone(){
        return (Object)this.MemberwiseClone();
    }
}

客户端调用代码:

static void Main(string[] args){
    Resume a= new Resume("大鸟");
    a.SetPersonalInfo("男","29");
    a.SetWorkExperience("1998-2000","XX公司");

    // 只需要调用Clone方法就可以实现新简历的生成,并且可以再修改新简历的细节
    Resume b= (Resume)a.Clone();
    b.SetWorkExperience("1998-2006","YY企业");

    Resume c =(Resume)a.Clone();
    c.SetPersonalInfo("男","24");

    a.Display();
    b.Display();
    c.Display();

    Console.Read();
}

结果显示:

一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能大大的提高。克隆等于是不用重新初始化对象,而是动态地获得对象运行时的状态

 

浅复制与深复制

现在简历对象里的数据都是string型的,而string是一种拥有值类型特点的特殊引用类型。MemberwiseClone()方法是这样,如果字段是值类型的,则对该字段进行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象

举个例子,现在的“简历“类中有一个”设置工作经历“的方法,在现实设计当中,一般会再有一个”工作经历“类,当中有”时间区间“和”公司名称“等属性,“简历”类直接调用这个对象即可。

 

第三个版本

代码结构图:

工作经历类:

// 工作经历
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;}
    } 
}

简历类:

// 简历
class Resume:ICloneable{
    private string name;
    private string sex;
    private string age;

    // 引用“工作经历”对象
    private WorkExperience work;

    public Resume(string name){
        this.name=name;
        // 在“简历”类实例化时间时实例化“工作经历”
        work=new WorkExperience();
    }

    // 设置个人信息
    public void SetPersonalInfo(string sex, string age){
        this.sex=sex;
        this.age=age;
    }
    // 设置工作经历
    public void SetWorkExperience(string workDate, string company){
        // 调用此方法时,给对象的两属性赋值
        work.WorkDate=workDate;
        work.Company=company;
    }

    // 显示
    public void Display(){
        // 显示时,显示“工作经历”的两属性的值
        Console.WriteLine("{0} {1} {2}",name,sex,age);
        Console.WriteLine("工作经历:{0} {1}",work.WorkDate,work.Company);
    }

    public Object Clone(){
        return (Object)this.MemberqiseClone();
    }
}

客户端调用代码:

static void Main(string[] args){
    Resume a = new Resume("大鸟");
    a.SetPersonalInfo("男","29");
    a.SetWorkExperience("1998-2000","XX公司");

    // b和c都克隆于a,但当它们都设置了“工作经历时”,
    // 我们希望的结果是三个的显示不一样
    Resume b= (Resume)a.Clone();
    b.SetWorkExperience("1998-2006","YY企业");

    Resume c = (Resume)a.Clone();
    c.SetWorkExperience("1998-2003","ZZ公司");

    a.Display();
    b.Display();
    c.Display();

    Console.Read();
}

结果显示:

由于MemberwiseClone是浅复制,被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。但我们可能更需要这样的一种需求,把要复制的对象所引用的对象都复制一遍,比如刚才的例子,我们希望是a、b、c三个引用的对象不同的复制时就一变二,二变三。此时,我们就叫这种方式为“深复制”,深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象

注意多层引用比较复杂,不要出现循环引用的问题。

 

最终版-简历的深复制实现

代码结构图:

工作经历类:

// 工作经历

// 让“工作经历”实现ICloneable接口
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();
    }
}

简历类:

// 简历
class Resume: ICloneable{
    private string name;
    private string sex;
    private string age;
    private WorkExperience work;
    
    public Resume(string name){
        this.name=name;
        work=new WorkExperience();
    }
    // 提供Clone方法调用的私有构造函数,
    // 以便克隆“工作经历”的数据
    private Resume(WorkExperience work){
        this.work=(WorkExperience)work.Clone();
    }
    // 设置个人信息
    public void SetPersonalInfo(string sex,string age){
        this.sex=sex;
        this.age=age;
    }
    // 设置工作经历
    public void SetWorkExperience(string workDate, string company){
        work.WorkDate=workDate;
        work.Company=company;
    }
    // 显示
    public void Display(){
        Console.WriteLine("{0} {1} {2}",name,sex,age);
        Console.WriteLine("工作经历:{0} {1}", work.WorkDate,work.Company);
    }
    public Object Clone(){
        // 调用私有的构造方法,让“工作经历”克隆完成
        // 然后再给这个“简历”对象的相关字段赋值
        // 最终返回一个深复制的简历对象
        Resume obj = new Resume(this.work);
        obj.name=this.name;
        obj.sex=this.sex;
        obj.age=this.age;
        return obj;
    }
}

同之前的客户端代码,其结果显示:

 

 

本章完。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是连载文章,此为第七章,学习从一个对象创建另外一个可定制的对象,而且不需知道任何创建的细节的原型模式。

上一章:https://blog.youkuaiyun.com/qq_36770641/article/details/82801288  工厂方法模式

下一章:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值