一、什么是原型模式
1.1 概述
一句话:用一个已经创建的实力作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式(Prototype Pattern)是用于
创建重复的对象,同时又能保证性能
。是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需依赖于构造函数。使用原型模式,可以在运行时动态地创建对象,避免了使用显式的构造函数进行对象创建的过程。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
在Java中,原型模式的实现通常需要实现Cloneable
接口,并重写clone()
方法。Cloneable
接口是一个标记接口,用于指示该类可以被克隆。
1.2 原型模式的结构
graph LR
A(原型模式)
B(抽象原型类)
C(具体原型类)
D(访问类)
E(规定了具体原型对象必须实现clone方法)
F(实现抽象原型类的clone方法,它是可被复制的对象)
G(使用具体原型类中的clone方法来复制新的对象)
A ---> B ---> E
A ---> C ---> F
A ---> D ---> G
style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px
style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px
二、Java中怎么实现原型模式
Java中原型模式的克隆分为深克隆和浅克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,扔纸箱原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
graph LR
A(Java实现原型模式)
B(浅克隆)
C(深克隆)
D(序列化和反序列化)
E(自定义克隆方法)
A ---> B
A ---> C
C ---> D
C ---> E
style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
2.1 在Java中如何实现浅克隆
Java中的克隆操作默认是浅拷贝,即复制对象本身,但不复制对象内部的引用类型数据。
下面是浅克隆实现的案例
- 创建一个可克隆的抽象基类或接口。该基类或接口将定义克隆方法,用于复制对象。
public abstract class Prototype implements Cloneable {
public abstract Prototype clone();
}
- 创建具体类,该类将实现克隆方法并定义对象的特定行为。(具体类中实现克隆方法。使用super.clone()方法来复制对象,并进行类型转换。)
public class ConcretePrototype extends Prototype {
@Override
public Prototype clone() {
try {
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- 在客户端代码中使用原型对象进行克隆操作。
public class Test {
public static void main(String[] args) {
Prototype original = new ConcretePrototype();
Prototype clone = original.clone();
}
}
2.2 在Java中如何实现深克隆
在Java中,有两种方法可以实现深克隆。
graph LR
A(java实现深克隆) ---> B(序列化和反序列化)
A(java实现深克隆) ---> C(自定义克隆方法)
style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
2.2.1 通过实现Serializable接口进行序列化和反序列化
-
在需要深克隆的类中实现Serializable接口。
-
将对象写入输出流并进行序列化。
-
从输入流中读取对象并进行反序列化。
import java.io.*;
public class Demo implements Serializable {
private String name;
private MyObject myObject;
// 构造函数和其他方法
public Demo deepCopy() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Demo) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
2.2.2 使用自定义的深克隆方法
-
在需要深克隆的类中实现自定义的深克隆方法。
-
在深克隆方法中创建一个新对象,并将原对象的属性值逐一复制到新对象中。对于引用类型的属性,可以使用其自身的深克隆方法或递归进行克隆。
public class Demo {
private String name;
private MyObject myObject;
// 构造函数和其他方法
public Demo deepCopy() {
Demo newObject = new Demo();
newObject.setName(this.name);
newObject.setMyObject(this.myObject.deepCopy()); // 假设MyObject类也实现了深拷贝方法
return newObject;
}
}
2.3 spring中那些非常好用的原型模式工具类
Spring框架还提供了其他一些原型模式相关的工具类和功能,包括以下几个:
graph LR
A(Spring提供的原型模式工具类)
B(ObjectFactory)
C(PrototypeScope)
D(ObjectProvider)
E(Lookup注解)
A ---> B
A ---> C
A ---> D
A ---> E
style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
ObjectFactory
: ObjectFactory是Spring提供的一个用于创建原型对象的接口,它可以用于在运行时动态地创建对象,而无需显式地调用构造函数。ObjectFactory接口有一个重要的方法getObject(),用于获取原型对象的实例。该方法在每次调用时都会返回一个新的对象实例,因此适用于创建原型对象。
PrototypeScope
: PrototypeScope是Spring框架中的一个作用域实现,用于定义原型作用域的Bean。可以通过在Bean定义中指定@Scope("prototype")注解或在XML配置中使用元素将Bean定义为原型作用域。使用PrototypeScope作用域,每次获取Bean时都会创建一个新的实例。
ObjectProvider
: ObjectProvider是Spring 4.3引入的一个接口,用于获取对象实例。它可以用于获取原型对象的实例,类似于ObjectFactory。ObjectProvider提供了更多的方法来获取和管理对象实例,例如getIfAvailable()、getIfUnique()等。
@Lookup注解
:@Lookup注解是Spring框架中的一个元注解,用于标记一个方法,表示该方法应该返回一个原型对象。使用@Lookup注解,可以在运行时动态地获取原型对象的实例。
示例:展示了如何在Spring中使用ObjectFactory创建原型对象:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class MyPrototype {
private static int count = 0;
private int id;
public MyPrototype() {
id = count++;
}
public int getId() {
return id;
}
}
public class PrototypeExample {
public static void main(String[] args) {
ObjectFactory objectFactory = new ObjectFactory() {
@Override
public MyPrototype getObject() {
return new MyPrototype();
}
};
// 使用ObjectFactory创建原型对象
MyPrototype obj1 = objectFactory.getObject();
MyPrototype obj2 = objectFactory.getObject();
System.out.println("Object 1 ID: " + obj1.getId());
System.out.println("Object 2 ID: " + obj2.getId());
}
}
在上面的示例中,使用ObjectFactory创建了一个MyPrototype的原型对象。每次调用getObject()方法时,都会返回一个新的MyPrototype对象实例。通过多次调用,可以看到每个对象实例都有不同的ID。
ObjectFactory接口可以在Spring框架中的各个地方使用,例如在Bean定义中声明原型作用域的Bean时,可以使用ObjectFactory来创建原型对象。
2.4 spring的BeanUtils.copyProperties 有用到原型设计模式吗
BeanUtils.copyProperties()方法并**
没有
**直接使用原型设计模式。它是Spring框架中的一个工具方法,用于将一个Java对象的属性值复制到另一个Java对象中。在BeanUtils.copyProperties()方法的实现中,并没有显式地使用原型模式的概念。它主要通过反射机制来获取和设置对象的属性值,然后将源对象的属性值复制到目标对象中。
然而,BeanUtils.copyProperties()方法在某种程度上**
可以看作是一种原型模式的变种
**。它通过复制属性值的方式创建一个新的目标对象,而不需要显式地调用构造函数。这种方式类似于原型模式中通过复制现有对象来创建新对象的方式,但并不是严格的原型模式实现。总的来说,尽管BeanUtils.copyProperties()方法在某种程度上具有类似原型模式的特征,但它并不是原型模式的典型实现。它更多地用于对象属性值的复制和转换,而不是动态创建对象实例。
三、原型模式的意义和Java中的使用场景
3.1 意义
在运行期建立和删除原型。
3.2 Java中的使用场景
graph LR
A(使用场景)
B(多线程环境)
C(缓存管理)
D(对象池管理)
E(动态代理)
A ---> B
A ---> C
A ---> D
A ---> E
style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
-
多线程环境
:在多线程环境中,原型模式工具类可以用于创建线程安全的对象实例。每个线程可以通过原型模式工具类获取自己的对象实例,避免了多个线程之间的竞争和共享状态的问题。 -
缓存管理
:原型模式工具类可以用于缓存管理,特别是在需要将某个对象作为模板复制多个对象的情况下。通过原型模式工具类,可以复制模板对象,并对每个复制的对象进行个性化的修改,从而实现自定义的缓存对象。 -
对象池管理
:在需要管理对象池的场景中,原型模式工具类可以用于创建和管理对象池中的对象。通过原型模式工具类,可以复制原型对象来填充对象池,并在需要时获取对象实例,避免了频繁创建和销毁对象的开销。 -
动态代理
:在动态代理中,原型模式工具类可以用于创建代理对象。每次需要代理对象时,可以通过原型模式工具类获取一个新的代理对象实例,以确保每个代理对象都具有独立的状态和行为。
四、原型模式的优缺点和注意事项
-
优点
-
性能提高。
-
逃避构造函数的约束。
-
-
缺点
-
配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
-
必须实现 Cloneable 接口。
-
-
注意事项
- 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。