原型模式属于创建型模式,主要作用是利用一个原型对象的克隆方法,在保证性能的情况下创建多个重复的对象,本质就是通过克隆一个原有的对象来复制出一个新对象。
原型模式主要是为了解决创建一个新对象时产生的大量资源消耗(如硬件资源或网络资源)问题,它首先记录一个创建好的对象作为原型对象,之后需要创建新对象时直接通过该原型对象的克隆方法快速的复制出一个新对象,这样就跳过了对象的初始化过程,自然减少了初始化的资源消耗。当创建新对象的成本比较大,而同类型的对象间差别不大时,就可以使用原型模式来创建。
在JAVA中可以使用Object clone()
方法实现对象的克隆操作,但是,一个类需要实现Cloneable
接口才能使用克隆方法,否则会报CloneNotSupportedException
异常。在此我将创建一个Sheep类,并要求循环创建一百万对象,为节省资源消耗,我决定使用原型模式进行实现。
package 设计模式.原型模式;
public class Sheep implements Cloneable {
private String name;
private String color;
private int age;
public Sheep(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
// 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
if (name != null){
name += "羊";
name = name.substring(name.length()-1);
}
}
@Override
protected Sheep clone(){
try {
return (Sheep) super.clone();
}catch (CloneNotSupportedException e){
// 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
System.out.println("克隆失败, 可能是未实现Cloneable接口!");
}
return null;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", age=" + age +
'}';
}
}
package 设计模式.原型模式;
public class Test {
public static void main(String[] args) {
long time = System.currentTimeMillis();
// 模拟在循环体中大量创建对象
for (int i = 0; i < 1000000 ; i++){
Sheep sheep = new Sheep("Dolly","white", 5);
}
System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
// 准备一个原型对象
Sheep prototypeObj = new Sheep("Dolly","white", 5);
time = System.currentTimeMillis();
// 模拟在循环体中大量创建对象
for (int i = 0; i < 1000000 ; i++){
Sheep sheep = prototypeObj.clone();
}
System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
}
}
原型模式还可以扩展为带有原型管理器的原型模式,并且克隆方法具有浅拷贝和深拷贝的区别。
原型管理器可以管理多个类的原型对象,方便这些类通过原型模式创建对象。其原理是通过一个HashMap
集合来管理每个原型对象,同时,为符合依赖倒置原则,多个原型类应当具实现同一个接口类,而管理器只需要对这个接口类进行管理即可。
package 设计模式.原型模式.原型管理器;
/**
* 继承克隆接口,这样实现该接口的类都能正常使用克隆方法
*/
public interface Animal extends Cloneable {
Animal clone();
String toString();
}
package 设计模式.原型模式;
import 设计模式.原型模式.原型管理器.Animal;
public class Sheep implements Animal {
…… 内部属性与方法都和之前写的一样
}
package 设计模式.原型模式.原型管理器;
public class Cat implements Animal {
private String name;
private String color;
private int age;
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
// 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
if (name != null){
name += "猫";
name = name.substring(name.length()-1);
}
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", age=" + age +
'}';
}
@Override
public Cat clone() {
try {
return (Cat) super.clone();
} catch (CloneNotSupportedException e){
}
return null;
}
}
package 设计模式.原型模式.原型管理器;
public class Dog implements Animal {
private String name;
private String color;
private int age;
public Dog(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
// 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
if (name != null){
name += "狗";
name = name.substring(name.length()-1);
}
}
@Override
public Dog clone() {
try {
return (Dog) super.clone();
} catch (CloneNotSupportedException e) {
}
return null;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", age=" + age +
'}';
}
}
package 设计模式.原型模式.原型管理器;
import 设计模式.原型模式.Sheep;
import java.util.HashMap;
public class PrototypeManager {
private HashMap<String, Animal> prototypeMap = new HashMap<>();
public PrototypeManager(){
put("cat", new Cat("Tom", "black", 7));
put("dog", new Dog("福贵", "黑白相间", 23));
put("sheep", new Sheep("Dolly","white", 5));
}
public void put(String key, Animal value){
prototypeMap.put(key, value);
}
public Animal get(String key){
Animal animal = prototypeMap.get(key);
if (animal != null){
return animal.clone();
}
return null;
}
}
package 设计模式.原型模式.原型管理器;
import 设计模式.原型模式.Sheep;
public class Test {
public static void main(String[] args) {
PrototypeManager prototypeManager = new PrototypeManager();
Cat cat = null;
Dog dog = null;
Sheep sheep = null;
long time = System.currentTimeMillis();
// 模拟在循环体中大量创建对象
for (int i = 0; i < 1000000; i++) {
cat = new Cat("Tom", "black", 7);
dog = new Dog("Dog", "white", 5);
sheep = new Sheep("Dolly", "white", 5);
}
System.out.println("通过构造方法new:");
System.out.println(cat);
System.out.println(dog);
System.out.println(sheep);
System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
time = System.currentTimeMillis();
// 模拟在循环体中大量创建对象
for (int i = 0; i < 1000000; i++) {
cat = (Cat) prototypeManager.get("cat");
dog = (Dog) prototypeManager.get("dog");
sheep = (Sheep) prototypeManager.get("sheep");
}
System.out.println("原型管理器创建:");
System.out.println(cat);
System.out.println(dog);
System.out.println(sheep);
System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
}
}
原型模式下可以分为浅拷贝和深拷贝。Java中存在基础类型和引用类型两种数据,浅拷贝在拷贝时会复制基础类型的值到新的变量中,但是,引用类型的数据是地址,就算把值复制到新的变量,地址值也还是指向原来的数据空间,所以浅拷贝下,引用数据不会被克隆,而是直接引用。若需改变引用地址,需要使用深拷贝。
浅拷贝无法为引用型属性复制一份新的数据地址空间,只是创建一个新的变量空间,然后将旧的引用数据地址复制给该变量。
package 设计模式.原型模式;
import 设计模式.原型模式.原型管理器.Animal;
public class Sheep implements Animal {
public String name;
public String color;
public int age;
public Sheep(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
// 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
if (name != null){
name += "羊";
name = name.substring(name.length()-1);
}
}
/**
* 浅拷贝
* @return 通过浅拷贝复制的新对象
*/
@Override
public Sheep clone(){
try {
return (Sheep) super.clone();
}catch (CloneNotSupportedException e){
// 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
System.out.println("克隆失败, 可能是未实现Cloneable接口!");
}
return null;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", age=" + age +
'}';
}
}
package 设计模式.原型模式.原型管理器;
import 设计模式.原型模式.Sheep;
public class CloneTest {
public static void main(String[] args) {
// 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
// 使用浅拷贝创建一个对象
Sheep sheep3 = sheep.clone();
System.out.println("sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
System.out.println("sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
System.out.println("sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
System.out.println("sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));
}
}
从截图结果来看,使用浅拷贝创建的新对象,所持有的引用型变量(String类型的name属性)于原型对象的引用型变量地址是相同的,那么就证明浅拷贝并不会复制一个新的引用型数据。
在Java中,深拷贝需要使用序列化与反序列化来实现。通过序列化写入到流中的对象,不仅其本身要实现序列化,而且它所有的引用型属性也要实现序列化操作,此时通过序列化复制到流中的对象,不仅克隆了自身也克隆了它的所有引用属性。当把此对象从流中反序列化读取出来时,就已经完成了深拷贝。
package 设计模式.原型模式;
import 设计模式.原型模式.原型管理器.Animal;
import java.io.*;
public class Sheep implements Animal,Serializable {
/**
* 深拷贝
* @return 通过深拷贝复制的对象
*/
public Sheep deepClone(){
// 写入流的对象必须实现Serializable序列化接口,其引用型属性也要实现,这里的引用型属性只有String类型,已经自动实现过序列化接口
ByteArrayOutputStream bos = null;
ByteArrayInputStream bis = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Sheep sheep = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
sheep = (Sheep) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
bos.close();
bis.close();
oos.close();
ois.close();
} catch (IOException e) {
}
}
return sheep;
}
…… 其他属性和方法与浅拷贝的Sheep类相同
}
package 设计模式.原型模式.原型管理器;
import 设计模式.原型模式.Sheep;
public class CloneTest {
public static void main(String[] args) {
// 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
// 使用浅拷贝创建一个对象
Sheep sheep3 = sheep.clone();
// 使用深拷贝创建一个对象
Sheep sheep4 = sheep.deepClone();
System.out.println("普通新建对象:");
System.out.println(" sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
System.out.println(" sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
System.out.println(" 因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
System.out.println("浅拷贝:");
System.out.println(" sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
System.out.println(" sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
System.out.println(" 因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));
System.out.println("深拷贝:");
System.out.println(" sheep和sheep4是否指向同一个地址:" + (sheep == sheep4));
System.out.println(" sheep的引用属性和sheep4的引用属性是否指向同一个地址:" + (sheep.name == sheep4.name));
System.out.println(" 因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep4.color));
}
}