目录
- 一、简介
- 二、原型模式
- 2.1、不使用原型模式
- 2.2、使用原型模式(浅拷贝)
- 2.3、使用原型模式(深拷贝)
- 三、原型模式
一、简介
原型模式(Prototype Pattern) 是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需向客户端暴露对象创建的细节。这种模式涉及使用原型实例作为要创建对象的种类,并通过复制这些原型来创建新的对象。
在原型模式中,客户端通过请求复制(克隆)原型实例来创建一个新的对象,而不是通过标准的对象构造方法。这使得创建新对象更加高效,因为它避免了创建对象的常规创建过程,如实例化和初始化。在实现上,原型模式通常需要目标类实现一个能够克隆自身的接口或方法(例如clone()方法),以便能够通过复制现有对象来生成新对象。
当涉及到对象的创建时,原型模式允许我们通过复制现有对象来创建新对象。原型模式一般是通过深浅拷贝来实现的。我们假设有个动物类 Animal 和食物类 Food
Animal.java
package com.alian.prototype;import java.io.Serializable;public abstract class Animal implements Serializable {public String name;public String sex;public abstract void sleep();public Animal() {}public Animal(String name, String sex) {this.name = name;this.sex = sex;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}
}
Food.java
package com.alian.prototype;import java.io.Serializable;public class Food implements Serializable {private String name;private int num;public Food() {}public Food(String name, int num) {this.name = name;this.num = num;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getNum() {return num;}public void setNum(int num) {this.num = num;}
}
接下来我们看看没有使用原型模式,和使用原型模式的区别。
二、原型模式
2.1、不使用原型模式
假设我们有一个狗类 Dog
Dog.java
package com.alian.prototype;import java.io.Serializable;public class Dog extends Animal implements Serializable {private int leg;private String breeds;public Dog() {}public Dog(String name, String sex, int leg, String breeds) {super(name, sex);this.leg = leg;this.breeds = breeds;}public int getLeg() {return leg;}public void setLeg(int leg) {this.leg = leg;}public String getBreeds() {return breeds;}public void setBreeds(String breeds) {this.breeds = breeds;}@Overridepublic void sleep() {System.out.println("睡觉要吹空调");}public static void main(String[] args) {Dog chineseRuralDog = new Dog("大毛", "雄性", 4, "中华田园犬");System.out.println(chineseRuralDog);// 如果需要创建新的用户对象,需要再次使用构造函数创建Dog labrador = new Dog("二毛", "雄性", 4, "拉布拉多");System.out.println(labrador);}
}
没有使用原型的时候每次都需要 new 一个对象来实现。
2.2、使用原型模式(浅拷贝)
实现浅拷贝的一个简单方式就是实现了 Cloneable 接口,并重写了 clone() 方法。我们用一个猫类 Cat 来说明浅拷贝。
package com.alian.prototype;import java.io.Serializable;public class Cat extends Animal implements Cloneable, Serializable {private int leg;private String breeds;private Food food;public Cat() {}public Cat(String name, String sex, int leg, String breeds) {super(name, sex);this.leg = leg;this.breeds = breeds;}public int getLeg() {return leg;}public void setLeg(int leg) {this.leg = leg;}public String getBreeds() {return breeds;}public void setBreeds(String breeds) {this.breeds = breeds;}public Food getFood() {return food;}public void setFood(Food food) {this.food = food;}@Overridepublic void sleep() {System.out.println("睡觉要吹空调");}@Overridepublic Cat clone() throws CloneNotSupportedException {return (Cat) super.clone();}public static void main(String[] args) {Cat cat = new Cat("三毛", "雌性", 4, "金吉拉猫");cat.setFood(new Food("鱼", 2));System.out.println("------------------------原对象属性-------------------------");System.out.println("原对象名称:" + cat.getName());System.out.println("原对象性别:" + cat.getSex());System.out.println("原对象脚的数量:" + cat.getLeg());System.out.println("原对象品种:" + cat.getBreeds());System.out.println("原对象口粮:" + cat.getFood().getName());System.out.println("原对象数量:" + cat.getFood().getNum());// 如果需要创建新的用户对象,需要再次使用构造函数创建try {// 使用原型模式创建新的用户对象,通过克隆方法复制现有用户对象Cat newCat = cat.clone();newCat.setName("四毛");newCat.setBreeds("曼基康猫");Food food2 = newCat.getFood();food2.setName("猫粮");food2.setNum(3);System.out.println("------------------------克隆对象属性-------------------------");System.out.println("克隆对象名称:" + newCat.getName());System.out.println("克隆对象性别:" + newCat.getSex());System.out.println("克隆对象脚的数量:" + newCat.getLeg());System.out.println("克隆对象品种:" + newCat.getBreeds());System.out.println("克隆对象口粮:" + newCat.getFood().getName());System.out.println("克隆对象数量:" + newCat.getFood().getNum());System.out.println("------------------------原对象属性-------------------------");System.out.println("原对象名称:" + cat.getName());System.out.println("原对象性别:" + cat.getSex());System.out.println("原对象脚的数量:" + cat.getLeg());System.out.println("原对象品种:" + cat.getBreeds());System.out.println("原对象口粮:" + cat.getFood().getName());System.out.println("原对象数量:" + cat.getFood().getNum());} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
运行结果:
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2
------------------------克隆对象属性-------------------------
克隆对象名称:四毛
克隆对象性别:雌性
克隆对象脚的数量:4
克隆对象品种:曼基康猫
克隆对象口粮:猫粮
克隆对象数量:3
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:猫粮
原对象数量:3
通过运行结果我们看到调用 clone() 方法后,然后重新设置了 Food 对象的属性,然后就发现拷贝对象是变了,同时也影响到了原对象的属性 Food (原对象的口粮从 鱼 变成 猫粮 ,数量 2 份变成 3 份)。这是为什么呢?
在Java语言中, clone() 方法执行的是浅拷贝,只会复制对象的基本数据类型(如int,String等,本例中就都没有改变),以及所引用的对象的内存地址,比如我们这里的 Food 的内存地址( 因为改变拷贝对象结果原对象也变了 ),不会递归地复制所引用的对象本身。
2.3、使用原型模式(深拷贝)
我们还是用上面的示例,不过一定要注意的是深拷贝一般父子类都要实现序列化接口 Serializable 。我们利用字节输入输出流来实现,代码如下:
CloneObject.java
package com.alian.prototype;import java.io.*;public class CloneObject {public static void main(String[] args) throws IOException, ClassNotFoundException {Cat cat = new Cat("三毛", "雌性", 4, "金吉拉猫");cat.setFood(new Food("鱼", 2));System.out.println("------------------------原对象属性-------------------------");System.out.println("原对象名称:" + cat.getName());System.out.println("原对象性别:" + cat.getSex());System.out.println("原对象脚的数量:" + cat.getLeg());System.out.println("原对象品种:" + cat.getBreeds());System.out.println("原对象口粮:" + cat.getFood().getName());System.out.println("原对象数量:" + cat.getFood().getNum());// 如果需要创建新的用户对象,需要再次使用构造函数创建// 使用原型模式创建新的用户对象,通过克隆方法复制现有用户对象Cat newCat = clone(cat);newCat.setName("四毛");newCat.setBreeds("曼基康猫");Food food2 = newCat.getFood();food2.setName("猫粮");food2.setNum(3);System.out.println("------------------------克隆对象属性-------------------------");System.out.println("克隆对象名称:" + newCat.getName());System.out.println("克隆对象性别:" + newCat.getSex());System.out.println("克隆对象脚的数量:" + newCat.getLeg());System.out.println("克隆对象品种:" + newCat.getBreeds());System.out.println("克隆对象口粮:" + newCat.getFood().getName());System.out.println("克隆对象数量:" + newCat.getFood().getNum());System.out.println("------------------------原对象属性-------------------------");System.out.println("原对象名称:" + cat.getName());System.out.println("原对象性别:" + cat.getSex());System.out.println("原对象脚的数量:" + cat.getLeg());System.out.println("原对象品种:" + cat.getBreeds());System.out.println("原对象口粮:" + cat.getFood().getName());System.out.println("原对象数量:" + cat.getFood().getNum());}private static <T extends Serializable> T clone(T obj) throws IOException, ClassNotFoundException {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(obj);ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);return (T) ois.readObject();}
}
运行结果:
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2
------------------------克隆对象属性-------------------------
克隆对象名称:四毛
克隆对象性别:雌性
克隆对象脚的数量:4
克隆对象品种:曼基康猫
克隆对象口粮:猫粮
克隆对象数量:3
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2
从结果看到深拷贝对象的属性完美的复制的,并且也没有影响原对象的属性,但是深拷贝明显要比浅拷耗时多,耗内存多。
三、原型模式
原型模式的优点和缺点如下:
优点:
- 对象复制: 允许在运行时复制现有对象,无需知道其具体实现。它可以在不影响客户端的情况下生成新对象。
- 减少资源消耗: 避免了对象的重新创建,提高了性能,特别是当创建对象的成本很高时。
- 简化对象创建: 通过克隆现有实例来创建新对象,避免了复杂的初始化步骤。适用于对象的初始化操作比较复杂的情况。
缺点:
- 深拷贝实现困难: 当对象中含有引用类型属性时,需要进行深拷贝才能保证拷贝对象与原始对象的属性完全独立。实现深拷贝可能比较困难。
- 破坏封装性: 原型模式需要对象实现克隆接口,这可能暴露对象的细节,破坏封装性。
- 需要理解对象的结构: 使用原型模式需要了解对象的内部结构,因为克隆会直接拷贝对象的字段。
- 可能产生多个相似对象: 如果拷贝出的对象过多,导致内存消耗增加。需要谨慎使用,避免滥用导致系统资源浪费。
原型模式适用于需要创建大量相似对象且创建过程复杂的场景,通过复制现有对象来提高效率。但在使用时需要注意深拷贝、克隆方式选择等问题,以避免潜在的性能问题和对象状态的不一致。