欢迎来到study-hard-forever的博客

6、原型模式

原型模式:

用原型实例指定创建对象的种类,并且通过拷贝这种原型创建新的对象。(从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节)

使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

原型模式UML结构图:

对于深复制与浅复制(深拷贝与浅拷贝)的简单区别:

浅拷贝:只负责克隆按值传递的数据(比如基本数据类型、String类型),所有引用的对象仍然指向原来的对象,即修改该引用对象时修改的是最初的那个对象。
深拷贝:除了浅拷贝要克隆的值外,还负责克隆引用类型(属性的类型也是对象)的数据,即把引用对象的变量指向复制过的新对象,而不是,而不是原有被引用的对象,即修改该引用对象时不会影响最初的那个对象。

我们在执行Clone函数本身时,实际其内部实现是浅复制,当我们需要的属性值是引用对象并需要作出修改时,我们就应该采用深复制的手段(重写Clone函数,使得每一步都是浅复制)

例(原型模式基本代码):

	abstract class Prototype
    {
        private string id;

        // Constructor 
        public Prototype(string id)
        {
            this.id = id;
        }

        // Property 
        public string Id
        {
            get { return id; }
        }

        public abstract Prototype Clone();  //关键在于此接口
    }

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

        public override Prototype Clone()
        {
            // Shallow copy 
            return (Prototype)this.MemberwiseClone();  //创建当前对象的浅复制副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,则对该字段执行逐位复制,如果该字段是引用类型,则复制其引用但是不复制引用的对象。
        }
    }


    class ConcretePrototype2 : Prototype
    {
        // Constructor 
        public ConcretePrototype2(string id)
            : base(id)
        {
        }

        public override Prototype Clone()
        {
            // Shallow copy 
            return (Prototype)this.MemberwiseClone();
        }
    }

	//Client:
	ConcretePrototype1 p1 = new ConcretePrototype1("I");
    ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone();

由于克隆实在太过常用,在.NET中System命名空间提供了ICloneable接口,其中唯一的方法就是Clone(),我们只需要实现这个接口就好了,就不用再新定义Prototype原型象类了。

例(简历复制——C#):

	//简历
    class Resume : ICloneable
    {
        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);
        }

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

上面的案例都是浅复制实现,如果我们涉及到引用对象时,就要采用深复制(有的时候引用里面嵌套这引用,导致引用有很多层,操作就会比较复杂,深复制需要深入到多少层需要事先就要考虑好,同时必须注意避免循环引用的问题,否则程序将会一直执行复制操作,陷入死循环)。

例(简历复制(深复制)——C#):

 	//简历
    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();
        }

        private Resume(WorkExperience work)  //提供给Clone函数调用的私有构造函数,以便克隆“工作经历”数据。
        {
            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;
        }

    }

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

在某些特定场合,会经常用到深复制和浅复制,比如在数据集对象DataSet,就有Clone()方法和Copy()方法,Clone()方法用来复制DataSet的结构但是不复制数据,实现了原型模式的浅复制,Copy()方法不但复制结构也复制数据,其实就是实现了原型模式的深复制。

Prototype模式与Factory模式的关系 :

虽然原型引入的初衷是像上面所说,但实现起来,却完全可以达到工厂模式的效果;而且,用起来甚至比工厂模式更方便、灵活。对于工厂模式与原形模式在功能上 的这点巧合,也许是因为本来工厂模式和 原型模式都是创建型模式,这样,他们的基本功能都能生成对象,因而使得原型模式在功能上可以代替工厂模式。

在原型模式中,你完全可以同样定义一个这样的 “抽象产品——具体产品”层次,再利用具体产品本身的clone功能来产生具体产品本身。从而达到实现工厂模 式功能的目的。实际上,在原型模式中,每个具体产品就扮演了工厂模 式里的具体工厂的角色(因为每个具体产品都具有生成自己拷贝的功能,从这种意义上讲,这正是工厂的作用)。
原型模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,而原型模式就不需要这样。

Prototype模式的最主要缺点就是每个原 型的子类都必须实现clone的操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归地让所有的相关对象都要正确地实现克隆。

下面我们对上面的内容强化练习一下(这次我们采用Java版本):

例(订单处理系统(深复制)——Java):

现在有一个订单处理系统,里面有一个保存订单的业务 功能,需求:每当订单的预定产品数量超过1000的时候,就需要把订单拆成两份订单来保存。如果拆成两份后还是超过1000,则继续拆分,直到每份产品预订数量不超过1000。
根据业务,目前的订单系统分成两种,一种是个人订单、一种是公司订单。
客户名称、产品对象(ID,Name),订购产品数量。
公司名称、产品对象(ID,Name),订购产品数量。

UML图:

//Product(产品类):

/*
 * 浅克隆(浅复制)是对要克隆的对象,其中的基本类型复制一份新的产生给对象。但是对于非基本类型的数据类型,仅仅是复制一份引用给新产生的对象。
 * 即基本类型是新产生的,非基本类型是复制一份内存引用。
 * 
 * 实现步骤:
 * 1.实现Cloneable接口
 * 要clone方法,为什么还要实现Cloneable接口呢?我们可以看看,Cloneable其中是一个空方法,它和RandomAccess一样都属于,标识接口。
 * 针对的是Object里的clone方法,进行重写。告诉Object而已。
 * 如果调用了super.clone()方法。没有实现Cloneable接口。那么会抛出异常CloneNotSupportedException异常。
 * 
 * 2.重写clone方法。
 * JDK中的API文档也说明了,这是一个对象拷贝的过程,不是对象的初始化过程。他包含了一些原有对象的信息。
 * Object中的clone的返回值是native。
 * native属于一般是由C语言实现,效率一般都高于java代码,所以这里我们没有选择new一个新的对象,而是选择对象拷贝。
 * 
 * Object中的clone修饰符是protected,我们要把它改成public。
 */

/*
 * 深度克隆,其实就是对于浅度克隆的一种完善,浅度克隆只能完成基本类型的克隆,对于非基本类型无法克隆,只能复制内存地址到新的对象上。
 * 而深度克隆不同,深度克隆是完成了,基本数据类型和非基本数据类型,都可以克隆。
 * 深复制重点在于:在主类上添加子类的Clone方法。(即多层浅复制)
 */

public class Product implements Cloneable {

	private int ID;
	private String name;
	public Product() {
		super();
	}
	public Product(int iD, String name) {
		super();
		ID = iD;
		this.name = name;
	}
	public int getID() {
		return ID;
	}
	public void setID(int iD) {
		ID = iD;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		//数据类型都是基本数据类型,执行浅复制
		return super.clone();	
	}
	
	@Override
	public String toString() {
		return "  ID:"+ID+" Name:"+name;
	}
}
//Order(订购顺序):

public class Order implements Cloneable {

	private int num;
	private String name;
	private Product product;
	public Order() {
		super();
	}
	public Order(int num, String name, Product product) {
		super();
		this.num = num;
		this.name = name;
		this.product = product;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Product getProduct() {
		return product;
	}
	public void setProduct(Product product) {
		this.product = product;
	}
	@Override
	public Object clone() throws CloneNotSupportedException {
		//首层浅复制
		Order order = (Order) super.clone();
		//相对于order的深复制(相对于product的浅复制)
		//深复制实质是多层浅复制
		order.setProduct((Product)product.clone());
		return order;
	}
	@Override
	public String toString() {
		return "订单数目:"+num+"  订单类型:"+name+product.toString();
	}
}

//main:

import java.util.ArrayList;
import java.util.List;

public class Test {
	 
    public static void main(String[] args) throws CloneNotSupportedException {
        int MAX=1000;
    	List<Order> list = new ArrayList<Order>();
        Product product = new Product(1, "产品1");
        Order order = new Order(3000,"公司订单",product);
        int y=order.getNum();
        while (y>MAX) {
            Order tmp = (Order) order.clone();
            tmp.setNum(MAX);
            list.add(tmp);
            y=order.getNum()-MAX;
            order.setNum(y);       
        }
        list.add(order);
        System.out.println("共有"+list.size()+"份订单");
        for (int i=0; i<list.size(); i++) {
            System.out.println(list.get(i).toString());
        } 
    }
}
posted @ 2020-06-19 00:46  study-hard-forever  阅读(127)  评论(0编辑  收藏  举报