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

来源:互联网 发布:数据选择器的工作原理 编辑:程序博客网 时间:2024/05/18 02:12

模式动机

       在软件开发中,有时某些对象创建的过程比较复杂,而且需要频繁创建,那么我们可不可以直接Ctrl C再Ctrl V呢?答案是可以的,原型模式就是解决这类问题,它通过复制一个对象本身,从而克隆出多个与原型对象一模一样的对象出来。

       模式动机

模式定义

       用原型实例指定创建对象的种类,然后通过复制这个原型对象创建出新的对象,无须知道创建的细节,这便是原型模式。

Java中的克隆

  java中所有的类都继承与java.lang.Object,这个类中有一个本地方法clone(),可以将一个java对象复制一份,因此在java中可以使用这个方法来实现对象的克隆,java中的原型模式的实现也因此变得很简单。另外,要想使用clone(),必须实现Cloneable接口,表示这个java类是支持复制的,如果一个类没有实现这个接口,但却调用了clone()方法,那将会报出CloneNotSupportedException的异常。

模式结构

模式结构
       原型模式主要由以下两部分组成:
  抽象原型类:如Prototype
  具体原型类:如ConcretePrototypeA

代码实例

  抽象原型类

public interface Prototype {    public Object copy();}

  具体原型类

public class Email implements Prototype,Cloneable{    private String title;    @Override    public Email copy() {        try {            return (Email) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return null;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }}

  客户端测试类

public class Client {    public static void main(String[] args) {        Email e1 = new Email();        e1.setTitle("我是邮件一");        Email e2 = e1.copy();        System.out.println(e2.getTitle());    }}

  运行客户端测试类,输出为

我是邮件一

  至此,一个最简单的原型模式已经完成。

深克隆和浅克隆

       在上面的例子中,我们的原型对象Email非常简单,里面只有一个title属性,假如里面包含各种类型,甚至包含一个对象,此时又该如何克隆呢?此时,有两种不同的克隆方式,浅克隆和深克隆。
       
       浅克隆也叫浅度拷贝或者浅度复制:只复制原对象里的8种基本类型及其封装类和String类型,对于对象类型,不会复制,新对象中仍然是原来的引用。
       
       深克隆也叫深度拷贝或者深度复制:不仅复制对象里的8种基本类型及其封装类和String类型,同时也复制原对象类型,完全产生新对象。下面通过实例来说明:

  • 浅克隆
 public class Attachment {    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}
public class Email implements Prototype,Cloneable{    private String title;    private int intA;    private Integer integerA;    private Attachment attachment;//这里是另外的一个对象    @Override    public Email copy() {        try {            return (Email) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return null;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public int getIntA() {        return intA;    }    public void setIntA(int intA) {        this.intA = intA;    }    public Integer getIntegerA() {        return integerA;    }    public void setIntegerA(Integer integerA) {        this.integerA = integerA;    }    public Attachment getAttachment() {        return attachment;    }    public void setAttachment(Attachment attachment) {        this.attachment = attachment;    }}
public class Client {    public static void main(String[] args) {        Email e1 = new Email();        e1.setTitle("我是title");        e1.setIntA(1);        e1.setIntegerA(new Integer(1));        Attachment attachment1 = new Attachment();        attachment1.setName("附件1");        e1.setAttachment(attachment1);        Email e2 = e1.copy();        System.out.println(e1.getAttachment() == e2.getAttachment());//true,说明是同一个引用,并没有复制一个新的对象        e1.setTitle("title发生改变");        System.out.println(e1.getTitle());//title发生改变        System.out.println(e2.getTitle());//我是title        //克隆对象e2的title并没有发生改变,说明String类型是复制了的        e1.setIntA(2);        System.out.println(e1.getIntA());//2        System.out.println(e2.getIntA());//1        //克隆对象e2的intA并没有发生改变,说明int类型是复制了的        e1.setIntegerA(new Integer(2));        System.out.println(e1.getIntegerA());//2        System.out.println(e2.getIntegerA());//1        //克隆对象e2的integerA并没有发生改变,说明Integer类型是复制了的        e1.getAttachment().setName("attachment的name发生改变");        System.out.println(e1.getAttachment().getName());//attachment的name发生改变        System.out.println(e2.getAttachment().getName());//attachment的name发生改变        //e1修改了attachment的name,e2的attachment的name也跟着变了,说明attachment并没有被复制,e1的attachment跟e2的attachment仍然是同一个。说明对于对象类型,调用clone()方法,并不会被复制,这就是浅克隆    }}
  • 深克隆
 //这里跟上面略有不同,这里需要实现Serializable接口,让其可以序列化,因为克隆的时候需要写到流里面 public class Attachment implements Serializable{    private static final long serialVersionUID = 1L;    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}
//这里也要实现Serializable接口public class Email implements Prototype,Serializable{    private static final long serialVersionUID = 1L;    private String title;    private int intA;    private Integer integerA;    private Attachment attachment;    @Override    public Email copy() {        try {            //这里的克隆方法跟浅克隆就完全不一样了,浅克隆是直接调用clone()方法即可,而深度克隆需要将对象写入流,然后再从流中取出来            //先把对象写入流            ByteArrayOutputStream bos = new ByteArrayOutputStream();            ObjectOutputStream oos = new ObjectOutputStream(bos);            oos.writeObject(this);            //然后把对象从流里取出来            byte b[] = bos.toByteArray();            ByteArrayInputStream bis = new ByteArrayInputStream(b);            ObjectInputStream ois = new ObjectInputStream(bis);            return (Email)ois.readObject();        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public int getIntA() {        return intA;    }    public void setIntA(int intA) {        this.intA = intA;    }    public Integer getIntegerA() {        return integerA;    }    public void setIntegerA(Integer integerA) {        this.integerA = integerA;    }    public Attachment getAttachment() {        return attachment;    }    public void setAttachment(Attachment attachment) {        this.attachment = attachment;    }}
public class Client {    public static void main(String[] args) {        Email e1 = new Email();        e1.setTitle("我是title");        e1.setIntA(1);        e1.setIntegerA(new Integer(1));        Attachment attachment1 = new Attachment();        attachment1.setName("附件1");        e1.setAttachment(attachment1);        Email e2 = e1.copy();        System.out.println(e1.getAttachment() == e2.getAttachment());//false,说明不是同一个引用!!!而是一个全新的对象!!!        e1.setTitle("title发生改变");        System.out.println(e1.getTitle());//title发生改变        System.out.println(e2.getTitle());//我是title        //克隆对象e2的title并没有发生改变,说明String类型是复制了的        e1.setIntA(2);        System.out.println(e1.getIntA());//2        System.out.println(e2.getIntA());//1        //克隆对象e2的intA并没有发生改变,说明int类型是复制了的        e1.setIntegerA(new Integer(2));        System.out.println(e1.getIntegerA());//2        System.out.println(e2.getIntegerA());//1        //克隆对象e2的integerA并没有发生改变,说明Integer类型是复制了的        e1.getAttachment().setName("attachment的name发生改变");        System.out.println(e1.getAttachment().getName());//attachment的name发生改变        System.out.println(e2.getAttachment().getName());//附件1        //e1修改了attachment的name,e2的attachment的name却没有随之改变,依然保持原来的值"附件1",说明attachment也被复制了,是一个全新的对象,这就是深克隆    }}

       对比上面两种代码可以看出,浅克隆和深克隆主要有以下不同:

  • 浅克隆不会复制对象类型的属性,深克隆要复制
  • 实现方式不一样,浅克隆是调用clone(),深克隆是使用输入输出流;
  • 浅克隆要实现Cloneable接口,而深克隆不用,但深克隆中原型对象以及里面所包含的对象,要实现Serializable接口

模式优点

  • 当创建的对象比较复杂时,原型模式可以简化创建对象的过程,直接通过一个实例复制得到;
  • 浅克隆是调用clone()方法,这是一个本地(native)方法,直接操作的内存中的二进制流,深度克隆也是通过输入输出流进行复制,性能上比通过new要好,所以当复制对象较大时,通过原型模式可以大大提升性能。

总结

       主要讲了几点,一是java中的clone()方法,必须要实现Cloneable接口;二是深克隆和浅克隆,主要区别在于复制对象类型时,浅克隆不会复制,而深克隆会复制;三是模式的优点,简化创建过程,同时提高了性能。

0 0