创建者模式之原型模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了创建者模式之原型模式相关的知识,希望对你有一定的参考价值。

原型模式:Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

实际上就是通过对原对象的一个拷贝,作为一个新对象,改变原来对象的一些属性,得到新的对象的过程,使用的原理就是java中的clone方法(c#里面也有一个clone方法,其他语言不太懂。c/c++应该是一个内存拷贝吧)。

看下面一段代码:

声明一个Ship类作为我们可以colne的类,因此继承Cloneable接口。代码如下:

import java.io.Serializable;
import java.util.Date;

public class Ship implements Cloneable, Serializable {
    public String name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private Date birthDay;
    
    
    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    public Object clone() throws CloneNotSupportedException {
        Object o = super.clone();
        return o;
    }
    
    public Ship(String name, Date birthDay){
        super();
        this.name = name;
        this.birthDay = birthDay;
    }
}

然后看一下使用类里面如何调用:

import java.util.Date;

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date(123445543L);
        Ship s1 = new Ship("多利", date);
        
        System.out.println(s1);
        System.out.println(s1.getName() + ":" + s1.getBirthDay());
        
        date.setTime(234243234L);
        
        Ship s2 = (Ship)s1.clone();
        s2.setName("多利的儿子");
        System.out.println(s2);
        System.out.println(s2.getName() + ":" + s2.getBirthDay());
    }
}

这样执行下来的结果是

原型模式[email protected]
多利:Fri Jan 02 18:17:25 CST 1970
原型模式[email protected]
多利的儿子:Sun Jan 04 01:04:03 CST 1970

说明已经不是一个类了,但是里面的参数是一样的(除了自己修改的),这样就能快速的得到一个新的对象,而不是通过new方法。

但是这里会有一个问题,就是浅拷贝和深拷贝的问题,这里是一个浅拷贝,也就是s1和s2使用的一个Date对象,他们复制的时候,复制的是引用,指向的地址还是一样的。如果调用改成这样:

 1 public static void main(String[] args) throws CloneNotSupportedException {
 2         Date date = new Date(123445543L);
 3         Ship s1 = new Ship("多利", date);
 4         Ship s2 = (Ship)s1.clone();
 5         
 6         date.setTime(234243234L);
 7         System.out.println(s1);
 8         System.out.println(s1.getName() + ":" + s1.getBirthDay());
 9         
10         s2.setName("多利的儿子");
11         System.out.println(s2);
12         System.out.println(s2.getName() + ":" + s2.getBirthDay());
13     }

输出是这样的:

1 原型模式[email protected]
2 多利:Sun Jan 04 01:04:03 CST 1970
3 原型模式[email protected]
4 多利的儿子:Sun Jan 04 01:04:03 CST 1970

通过这两个调用就可以看出来这是一个浅拷贝。我们下面看一下深拷贝是这么完成的。

1 public Object clone() throws CloneNotSupportedException {
2         Object o = super.clone();
3         
4         //添加如下代码实现深复制(deep Clone)
5         Ship s = (Ship)o;
6         s.setBirthDay((Date)this.birthDay.clone());//把属性也进行克隆!
7         
8         return o;
9     }

改变clone方法,添加了两行代码,再来看一下结果

1 原型模式[email protected]
2 多利:Sun Jan 04 01:04:03 CST 1970
3 原型模式[email protected]
4 多利的儿子:Fri Jan 02 18:17:25 CST 1970

两个时间已经不同了,说明在给s1设置时间的时候,没有对s2的Date对象改变,也就是s2和s1的Date指向的是不同的内容。这就实现了深拷贝。

其实关于深拷贝的方法还可以使用序列化来实现,要想让我们的类(Ship类)可以序列化,必须继承Serializable接口。我们来看一下

 1 public class Main2 {
 2     public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
 3         Date date = new Date(123445543L);
 4         Ship s1 = new Ship("多利", date);
 5                 
 6         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 7         ObjectOutputStream      oos = new ObjectOutputStream(bos);
 8         oos.writeObject(s1);
 9         byte[] bytes = bos.toByteArray();
10         
11         ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
12         ObjectInputStream ois = new ObjectInputStream(bis);
13             
14         Ship s2 = (Ship) ois.readObject();
15         
16         date.setTime(234243234L);
17         System.out.println(s1);
18         System.out.println(s1.getName() + ":" + s1.getBirthDay());
19         
20         s2.setName("多利的儿子");
21         System.out.println(s2);
22         System.out.println(s2.getName() + ":" + s2.getBirthDay());
23     }
24 }

上面的代码使用序列化将s1对象保存在了byte数组中,然后将byte数组的数据反序列化得到对象赋值给s2。改变了s1的时间之后,s2的时间并没有发生变化,说明这是一个深复制的过程。

最后说一下原型模式的意义是什么呢?其实为了解决构造函数非常复杂,构造需要很长时间的时候,我们可以使用原型模式节省大量的时间。如果我们在一个类的构造函数里面设置一个Sleep(10),构造1000次(表示这个构造函数非常复杂)。使用一般的new和我们的原型模式产生新的对象来进行比较的话,会发现原型模式非常快,而一般的new非常的慢。

除此之外,在Spring中,构造函数的方法只有两种,一个是单例,一个是原型模式。在工厂模式中,不是用new生产对象(单例还是需要new的),是用原型模式更快的构建对象。

以上是关于创建者模式之原型模式的主要内容,如果未能解决你的问题,请参考以下文章

一天学习一个设计模式之原型模式

设计模式之原型模式

go设计模式之原型模式

PHP设计模式之工厂模式和原型模式

设计模式之原型

设计模式之-创建模式-原型模式