原型模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效。
原型模式的定义与特点
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
原型模式的结构与实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
1. 模式的结构
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
2. 模式的实现
在JAVA里,通过克隆(Clone())方法来实现原型模式。
任何类,要想支持克隆,必须实现一个接口 Cloneable,该接口中有clone()方法,可以在类中重写自定义的克隆方法。
克隆的实现方法有三种:
- 浅拷贝:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对直接引用进行拷贝,没有对直接引用指向的对象进行拷贝。
- 深拷贝:深拷贝是指在拷贝对象时,不仅把基本数据类型的变量会重新复制一份,同时会对引用指向的对象进行拷贝。
- 完全拷贝:在包括上面两者共同点的基础上把对象间接引用的对象也进行拷贝。这是最彻底的一种拷贝。通常先使对象序列化,实现Serializable接口,然后将对象写进二进制流里 再从二进制流里读出新对象。
浅拷贝
创建ShallowCopy 对象类 ,重写clone()方法
package prototype;
public class ShallowCopy implements Cloneable{
private String name;
public ShallowCopy(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected ShallowCopy clone(){
ShallowCopy shallowCopy=null;
try {
shallowCopy=(ShallowCopy)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return shallowCopy;
}
}
测试类:
package prototype;
public class Clone {
public static void main(String[] args) {
shallowCopy();
//deepCopy();
}
public static void shallowCopy(){
ShallowCopy shallowCopy = new ShallowCopy("浅拷贝"); //新建一个对象 ,这个对象用作被克隆的原型
ShallowCopy copy = shallowCopy.clone();
System.out.println("提供的原型对象名称: "+shallowCopy.getName());
System.out.println("克隆的的对象名称: "+copy.getName());
System.out.println("两个对象"+(shallowCopy == copy?"相同":"不同")); //测试被克隆的对象与原对象是否是同一个对象
}
运行结果
提供的原型对象名称: 浅拷贝
克隆的的对象名称: 浅拷贝
两个对象不同
深拷贝
创建DeepCopy 对象类 ,为其添加地址shallowCopy成员对象,重写clone()方法
package prototype;
public class DeepCopy implements Cloneable{
private String name;
ShallowCopy shallowCopy;
public DeepCopy(String name, ShallowCopy shallowCopy) {
this.name = name;
this.shallowCopy = shallowCopy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected DeepCopy clone(){
DeepCopy deepCopy=null;
try {
deepCopy=(DeepCopy)super.clone();
deepCopy.shallowCopy= shallowCopy.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return deepCopy;
}
}
测试类:
package prototype;
public class Clone {
public static void main(String[] args) {
//shallowCopy();
deepCopy();
}
public static void deepCopy(){
ShallowCopy shallowCopy = new ShallowCopy("浅拷贝");
DeepCopy deepCopy = new DeepCopy("深拷贝",shallowCopy);
DeepCopy copy = deepCopy.clone();
System.out.println("提供的原型对象名称:" + deepCopy.getName() + " 克隆的的对象名称:" + copy.getName());
System.out.println("原型对象的引用对象:" + deepCopy.shallowCopy.getName() + " 克隆对象的引用对象:" + copy.shallowCopy.getName());
System.out.println("两个对象"+(deepCopy == copy?"相同":"不同")); //测试被可伶的对象与原对象是否是同一个对象
System.out.println("修改原型对象名称为深拷贝-1,其引用对象为浅拷贝-1");
deepCopy.setName("深拷贝-1");
deepCopy.shallowCopy.setName("浅拷贝-1");
System.out.println("提供的原型对象名称:" + deepCopy.getName() + " 克隆的的对象名称:" + copy.getName());
System.out.println("原型对象的引用对象:" + deepCopy.shallowCopy.getName() + " 克隆对象的引用对象:" + copy.shallowCopy.getName());
// 浅复制只复制值类型的变量和对对象的引用
// 深复制不仅复制值类型的变量,把原对象引用的对象也进行复制.
}
}
运行结果
提供的原型对象名称:深拷贝 克隆的的对象名称:深拷贝
原型对象的引用对象:浅拷贝 克隆对象的引用对象:浅拷贝
两个对象不同
修改原型对象名称为深拷贝-1,其引用对象为浅拷贝-1
提供的原型对象名称:深拷贝-1 克隆的的对象名称:深拷贝
原型对象的引用对象:浅拷贝-1 克隆对象的引用对象:浅拷贝
完全拷贝
新建CompleteCopy类,实现 Serializable 接口 创建completeCopy()方法 这里要记住该对象引用的对象 ShallowCopy类也需要实现 Serializable 接口
package prototype;
import java.io.*;
public class CompleteCopy implements Serializable{
private static final long serialVersionUID = -1L;
private String name;
ShallowCopy shallowCopy;
public CompleteCopy(String name, ShallowCopy shallowCopy) {
this.name = name;
this.shallowCopy = shallowCopy;
}
public CompleteCopy(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CompleteCopy completeCopy() throws IOException,ClassNotFoundException,OptionalDataException
{
CompleteCopy completeCopy=null;
// 将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
// 将对象从流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (CompleteCopy) ois.readObject();
}
}
测试类:
package prototype;
import java.io.IOException;
public class Clone {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ShallowCopy shallowCopy = new ShallowCopy("浅拷贝");
CompleteCopy completeCopy = new CompleteCopy("完全拷贝",shallowCopy);
CompleteCopy copy = completeCopy.completeCopy();
System.out.println("提供的原型对象名称:" + completeCopy.getName() + " 克隆的的对象名称:" + copy.getName());
System.out.println("原型对象的引用对象:" + completeCopy.shallowCopy.getName() + " 克隆对象的引用对象:" + copy.shallowCopy.getName());
System.out.println("修改原型对象名称为完全拷贝-1,其引用对象为浅拷贝-1");
completeCopy.setName("完全拷贝-1");
completeCopy.shallowCopy.setName("浅拷贝-1");
System.out.println("提供的原型对象名称:" + completeCopy.getName() + " 克隆的的对象名称:" + copy.getName());
System.out.println("原型对象的引用对象:" + completeCopy.shallowCopy.getName() + " 克隆对象的引用对象:" + copy.shallowCopy.getName());
// 浅复制只复制值类型的变量和对对象的引用
// 深复制不仅复制值类型的变量,把原对象引用的对象也进行复制.
}
}
运行结果
提供的原型对象名称:完全拷贝 克隆的的对象名称:完全拷贝
原型对象的引用对象:浅拷贝 克隆对象的引用对象:浅拷贝
两个对象不同
修改原型对象名称为完全拷贝-1,其引用对象为浅拷贝-1
提供的原型对象名称:完全拷贝-1 克隆的的对象名称:完全拷贝
原型对象的引用对象:浅拷贝-1 克隆对象的引用对象:浅拷贝
由此可见 完全拷贝将某对对象直接引用对象,包括引用对象引用的对象都重新复制,不需要实现Cloneable接口,重写Clone()方法,较为简单
Spring对原型模式的底层实现
bean的scope:有一种prototype,容器在接受到该类型对象请求的时候,都会重新生成一个新的对象实例给请求方,该标志内部实现就是实用来了原型模式,完成对象的生成。
/**
* Actually create the specified bean. Pre-creation processing has already happened
* at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
* <p>Differentiates between default bean instantiation, use of a
* factory method, and autowiring a constructor.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args explicit arguments to use for constructor or factory method invocation
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
* @see #instantiateBean
* @see #instantiateUsingFactoryMethod
* @see #autowireConstructor
*/
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
原型模式的扩展
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。其结构图如图所示。
在PrototypeManager中定义了一个HashMap类型的集合对象,使用“键值对”来存储原型对象,客户端可以通过Key(如“far”或“srs”)来获取对应原型对象的克隆对象。PrototypeManager类提供了类似工厂方法的getPrototype()方法用于返回一个克隆对象。在实际应用中,可以使用ConcurrentHashMap(Java 5或以上)集合来保证线程安全,将PrototypeManager设计为单例类,使用饿汉式单例实现,确保系统中有且仅有一个PrototypeManager对象,有利于节省系统资源,并可以更好地对原型管理器对象进行控制。
原型模式的扩展
原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操作就是原型模式的典型应用,下面对该模式的使用效果和适用情况进行简单的总结。
1.主要优点
原型模式的主要优点如下:
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
- 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
2.主要缺点
原型模式的主要缺点如下:
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
3.适用场景
在以下情况下可以考虑使用原型模式:
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。