51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Java 设计模式之原型模式(五)

一、前言 {#一、前言}

本篇介绍 Java 设计模式中创建型模式的最后一种--原型模式。上篇设计模式主题为 《Java 设计模式之建造者模式(四)》

二、简单介绍 {#二、简单介绍}

原型模式是一种对象创建型模式,它采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。

2.1 特点 {#2.1-特点}

  1. 由原型对象自身创建目标对象。即对象创建这一动作发自原型对象本身。

  2. 目标对象是原型对象的一个克隆。即通过原型模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值。

  3. 根据对象克隆深度层次的不同,有浅度克隆与深度克隆。

2.2 应用场景 {#2.2-应用场景}

  1. 在创建对象的时候,我们不只是希望被创建的对象继承其父类的基本结构,还希望继承原型对象的数据。

  2. 希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。

  3. 隐藏克隆操作的细节。很多时候,对对象本身的克隆需要涉及到类本身的数据细节。

三、实现方式 {#三、实现方式}

我们以克隆羊为例,实现类需要实现 Cloneable 接口,同时要重写从 Object 类中继承的 clone 方法:

|------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class Sheep implements Cloneable{ private String name; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } |

客户端:

|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class Client { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); Sheep clone = (Sheep) sheep.clone(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); } } |

结果:

|-------------|-----------------------------------------------------| | 1 2 | 克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 |

当我们需要更多的羊时,只需调用第一只羊的 clone 方法即可,省去了给新建的对象设置属性的操作。

不过,上边的克隆方式属于浅度克隆,当对象中包含引用类型的属性时,这样浅克隆方式会存在一个问题。我们试着修改 date 的值:

|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Client { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); Sheep clone = (Sheep) sheep.clone(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); System.out.println("--------------------------------"); // 修改本体羊生日 date.setTime(3246584261356L); System.out.println("本体羊生日:" + sheep.getBirthday()); System.out.println("克隆羊生日:" + clone.getBirthday()); } } |

结果打印:

|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | 克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 -------------------------------- 本体羊生日:Thu Nov 17 12:57:41 CST 2072 克隆羊生日:Thu Nov 17 12:57:41 CST 2072 |

我们修改本体羊的日期,结果克隆羊的日期也发生变化。

因为浅度克隆只对基本数据类型的值进行备份,而引用类型的数据只是拷贝了指向对象的引用而已。其内存表现形式如下图:

从图中可知,两只羊共用一个日期对象。因此,当我们修改日期时,两只羊的日期也发生变化。

为了解决这一个问题,我们可以使用深度克隆方式,有 2 种方式可以实现深度克隆。

方式一:在 clone 方法中将所有引用类型数据进行克隆。

实体类:

|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Sheep sheep = (Sheep) obj; // 克隆时间 sheep.birthday = (Date) this.birthday.clone(); return sheep; } |

在实体类的 clone 方法中,将引用类型的数据一起克隆。

在运行客户端代码时,结果如下:

|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | 克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 -------------------------------- 本体羊生日:Thu Nov 17 12:57:41 CST 2072 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 |

方式二:使用序列化和反序列化进行克隆

因为所有序列化方式,因此实体必须实现 Serializable 接口。

实体类:

|---------------|---------------------------------------------------------------------| | 1 2 3 | public class Sheep implements Cloneable,Serializable{ ... } |

客户端:

|------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class Client { public static void main(String[] args) throws CloneNotSupportedException,ClassNotFoundException, IOException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); //通过序列化和反序列化进行克隆 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(sheep); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Sheep clone = (Sheep) ois.readObject(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); System.out.println("--------------------------------"); // 修改本体羊生日 date.setTime(3246584261356L); System.out.println("本体羊生日:" + sheep.getBirthday()); System.out.println("克隆羊生日:" + clone.getBirthday()); } } |

打印结果与方式一的结果一致。

深度克隆的内存表现形式如下图:

四、性能比较 {#四、性能比较}

使用原型模式创建对象并不会调用类的构造方法。因此,在构造函数调用长且需要重复创建该类的实例的情况下,使用原型模式创建对象的优势就体现出来了。

实体类:

|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class Sheep implements Cloneable{ private String name; private Date birthday; public Sheep() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } |

延迟构造器调用的时间。

客户端:

|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Client { public static void main(String[] args) throws CloneNotSupportedException { Sheep sheep = new Sheep(); int num = 1000; long t1 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { Sheep tmp = new Sheep(); } System.out.println("new 方式耗时:" + (System.currentTimeMillis() - t1) + "ms"); long t2 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { Sheep tmp = (Sheep) sheep.clone(); } System.out.println("克隆方式耗时:" + (System.currentTimeMillis() - t2) + "ms"); } } |

结果:

|-------------|-------------------------------------| | 1 2 | new 方式耗时:10298ms 克隆方式耗时:1ms |

赞(0)
未经允许不得转载:工具盒子 » Java 设计模式之原型模式(五)