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