文章目录
- 1 基本介绍
- 2 实现方式
- 深浅拷贝
- 目标
- 2.1 使用 Object 的 clone() 方法
- 2.1.1 代码
- 2.1.2 特性
- 2.1.3 实现深拷贝
- 2.2 在 clone() 方法中使用序列化
- 2.2.1 代码
- 2.2.2 特性
- 3 实现的要点
- 4 Spring 中的原型模式
- 5 原型模式的类图及角色
- 5.1 类图
- 5.1.1 不限制语言
- 5.1.2 在 Java 中的类图
- 5.2 角色
- 5.2.1 Prototype ( 原型 )
- 5.2.2 ConcretePrototype ( 具体的原型 )
- 5.2.3 Client ( 使用者 )
- 6 原型模式的优缺点
- 7 原型模式的使用场景
- 8 总结
1 基本介绍
原型模式(Prototype Pattern)是一种创建型设计模式,它允许 通过复制已有的对象来创建新的对象,而无需知道对象创建的细节。
2 实现方式
深浅拷贝
原型模式的实现围绕着 深浅拷贝 的特性展开,其定义如下:
- 浅拷贝(Shallow Copy):只复制对象的 第一层 属性(即 八大基本数据类型 +
String
这个引用数据类型),对于引用类型的属性(除了字符串String
类型外),复制的是内存地址引用,而非对象本身。它存在这种危险:如果原对象的引用类型属性被修改,浅拷贝得到的对象的对应属性也会受到影响。 - 深拷贝(Deep Copy):递归地 复制对象及其所有子对象,创建一个全新的对象,与原对象没有任何关联。
目标
实现一个 Sheep
类,字段为 String name
和 int age
,它能通过 clone()
方法克隆出一模一样的对象。
共有以下 2 种实现:
2.1 使用 Object 的 clone() 方法
2.1.1 代码
public class Sheep implements Cloneable { // 实现了 Cloneable 接口
private String name;
private int age;
private String friend;
public Sheep(String name, int age, String friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getFriend() {
return friend;
}
@Override
public Sheep clone() {
try {
return (Sheep) super.clone(); // 使用 Object 的 clone()
} catch (Exception e) { // 如果有异常
e.printStackTrace(); // 打印异常
return null; // 并返回 null
}
}
public static void main(String[] args) { // 测试程序
Sheep sheep = new Sheep("silvery", 6, new Sheep("gold", 13, null));
Sheep clone = sheep.clone();
System.out.println(sheep == clone);
System.out.println("[sheep]: name = " + sheep.getName() + ", age = " + sheep.getAge()
+ ", friend = " + sheep.getFriend() + ", friend.name = " + sheep.getFriend().getName());
System.out.println("[clone]: name = " + clone.getName() + ", age = " + clone.getAge()
+ ", friend = " + clone.getFriend() + ", friend.name = " + clone.getFriend().getName());
/* 测试结果如下:
false
[sheep]: name = silvery, age = 6, friend = cn.me.Sheep@f6f4d33, friend.name = gold
[clone]: name = silvery, age = 6, friend = cn.me.Sheep@f6f4d33, friend.name = gold
总结:
sheep 和 name 不是同一个对象,但属性一样
它们的 friend 是同一个 friend,这就是 浅拷贝
*/
}
}
2.1.2 特性
- 浅拷贝:
Object
自带的clone()
方法是浅拷贝,对于非String
类型的引用类型,它只能拷贝其内存地址。 - 实现简单:虽然这种方式是浅拷贝,但实现起来很简单,如果能确定不使用除
String
类型之外的引用类型,这种方式很方便。
2.1.3 实现深拷贝
如果一个类中除 String
类型之外的引用类型 很少,那么可以像以下这样 单独 对这些引用类型进行 克隆,从而实现深拷贝:
@Override
public Sheep clone() {
try {
Sheep clone = (Sheep) super.clone();
// 对 除 String 之外的引用类型 单独克隆
// 如果没有判断空值,则可能会抛出空指针异常 NullPointerException
if (clone.friend != null) {
clone.friend = this.friend.clone();
}
return clone;
} catch (Exception e) { // 如果有异常
e.printStackTrace(); // 打印异常
return null; // 并返回 null
}
}
2.2 在 clone() 方法中使用序列化
2.2.1 代码
注意:如果要使用序列化,则要实现 Serializable
接口。
public class Sheep implements Cloneable, Serializable { // 实现了 Cloneable 和 Serializable 接口
private String name;
private int age;
private String friend;
public Sheep(String name, int age, String friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getFriend() {
return friend;
}
@Override
public Sheep clone() {
ByteArrayOutputStream bos = null; // 字节数组输出流
ObjectOutputStream oos = null; // 对象输出流
ByteArrayInputStream bis = null; // 字节数组输入流
ObjectInputStream ois = null; // 对象输入流
try {
// 序列化:将当前对象以对象流的形式输出
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化:将当前对象以对象流的形式读取,形成一个新的对象
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Object clone = ois.readObject();
return (Sheep) clone;
} catch (Exception e) { // 如果有异常
e.printStackTrace(); // 打印异常
return null; // 并返回 null
} finally {
try { // 用完流之后记得关闭
if (bos != null) {
bos.close();
}
if (oos != null) {
oos.close();
}
if (bis != null) {
bis.close();
}
if (ois != null) {
ois.close();
}
} catch (Exception e) { // 如果关闭流出现异常
e.printStackTrace(); // 则打印异常
}
}
}
public static void main(String[] args) { // 测试程序
Sheep sheep = new Sheep("silvery", 6, new Sheep("gold", 13, null));
Sheep clone = sheep.clone();
System.out.println(sheep == clone);
System.out.println("[sheep]: name = " + sheep.getName() + ", age = " + sheep.getAge()
+ ", friend = " + sheep.getFriend() + ", friend.name = " + sheep.getFriend().getName());
System.out.println("[clone]: name = " + clone.getName() + ", age = " + clone.getAge()
+ ", friend = " + clone.getFriend() + ", friend.name = " + clone.getFriend().getName());
/* 测试结果如下:
false
[sheep]: name = silvery, age = 6, friend = cn.me.Sheep@421faab1, friend.name = gold
[clone]: name = silvery, age = 6, friend = cn.me.Sheep@504bae78, friend.name = gold
总结:
sheep 和 name 不是同一个对象,但属性一样
它们的 friend 不是同一个 friend,但属性一样,这是 深拷贝
*/
}
}
2.2.2 特性
- 深拷贝:这种方式是深拷贝,它会递归地构建所有对象,从而创建一个与原对象无任何关联的新对象,只是属性相同罢了。
- 实现复杂:这种方式的实现很复杂,涉及到四个流的创建、使用和关闭,要处理的异常也很多。
- 性能开销:这种方式由于需要创建、使用和关闭流,所以可能会消耗一部分性能。
3 实现的要点
- 实现
Cloneable
接口。 - 重写
Object
类的clone()
方法。 - 考虑深浅拷贝的问题。
4 Spring 中的原型模式
对于 ClassPathXmlApplicationContext
类的对象,它的 getBean()
方法继承自 AbstractBeanFactory
抽象类的 getBean()
方法,其内部调用了 doGetBean()
方法,在 doGetBean()
方法中使用了原型模式(这里面具体的逻辑就很复杂了),如下所示:
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args,
boolean typeCheckOnly) throws BeansException {
// ...
else if (mbd.isPrototype()) {
prototypeInstance = null;
Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
prototypeInstance = this.createBean(beanName, mbd, args); // 在这里使用了原型模式
} finally {
this.afterPrototypeCreation(beanName);
}
beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// ...
}
5 原型模式的类图及角色
5.1 类图
5.1.1 不限制语言
5.1.2 在 Java 中的类图
5.2 角色
5.2.1 Prototype ( 原型 )
该角色负责 定义 用于复制现有实例来生成新实例的 方法,在 Java 中一般指的是 Object
类,它定义了 native
的 clone()
方法。
5.2.2 ConcretePrototype ( 具体的原型 )
该角色负责 实现 用于复制现有实例来生成新实例的 方法,在 Java 中不仅要继承 Object
类(如果一个类不指定继承的类,则它默认继承 Object
类),还要实现 Cloneable
这个 标记接口,虽然它没有定义方法,但要使用 clone()
方法就得实现它,否则就会抛出 CloneNotSupportedException
(不支持 clone()
方法)的异常。
5.2.3 Client ( 使用者 )
该角色负责 使用 用于复制现有实例来生成新实例的 方法。
6 原型模式的优缺点
优点:
- 性能高:使用原型模式复用的方式创建实例对象,比使用构造器重新创建对象性能要高。
- 流程简单:原型模式可以简化创建的过程,可以直接修改现有的对象实例的值,达到复用的目的。
- 具有动态性:使用构造器创建对象的代码在运行期间时固定的,而使用原型模式可以获取对象运行时的属性,从而 动态地 创建出新的对象。
缺点:
- 实现复杂:必须重写对象的
clone()
方法,且需要考虑 深拷贝 与 浅拷贝 的风险。 - 克隆方法需要通盘考虑:配备克隆方法需要对类的功能进行通盘考虑,特别是对于已有的类,可能需要修改其结构以支持克隆,违背了 开闭原则。
7 原型模式的使用场景
- 对象的创建成本较高:如果创建对象的过程比较复杂或耗时较长,可以使用原型模式通过复制一个现有对象的属性和方法来创建新对象,从而避免昂贵的创建过程。
- 需要创建大量相似的对象:如果需要创建大量相似的对象,可以先创建一个原型对象,然后通过复制原型对象来创建新对象,从而提高对象创建的效率和性能。
- 对象的修改频繁:如果对象的属性需要经常变化,而且每次变化都需要创建一个新的对象,可以使用原型模式,通过复制原型对象来创建新对象并修改其属性,而不需要每次都重新创建新对象。
- 隐藏对象的创建细节:如果创建对象的细节比较复杂,不希望客户端直接与创建对象的过程耦合,可以使用原型模式,客户端只需要通过复制一个已有对象来创建新对象,而无需知道创建的细节。
8 总结
原型模式是一种非常有用的 创建型 设计模式,它 通过复制已有的对象来创建新对象,从而 提高了对象创建的效率和性能。然而,在使用时需要注意 深浅拷贝问题 以及 克隆方法的实现细节,以避免出现不必要的错误。