Java设计模式-原型模式

2023-11-13

原型模式

  在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效。

原型模式的定义与特点

  原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

原型模式的结构与实现

  由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

1. 模式的结构

原型模式包含以下主要角色。

  1. 抽象原型类:规定了具体原型对象必须实现的接口。
  2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

 原型模式的结构图

2. 模式的实现

  在JAVA里,通过克隆(Clone())方法来实现原型模式。
  任何类,要想支持克隆,必须实现一个接口 Cloneable,该接口中有clone()方法,可以在类中重写自定义的克隆方法。
  克隆的实现方法有三种:

  1. 浅拷贝:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对直接引用进行拷贝,没有对直接引用指向的对象进行拷贝。
  2. 深拷贝:深拷贝是指在拷贝对象时,不仅把基本数据类型的变量会重新复制一份,同时会对引用指向的对象进行拷贝。
  3. 完全拷贝:在包括上面两者共同点的基础上把对象间接引用的对象也进行拷贝。这是最彻底的一种拷贝。通常先使对象序列化,实现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.主要优点

   原型模式的主要优点如下:

  1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  2. 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
  3. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
  4. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
2.主要缺点

  原型模式的主要缺点如下:

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
3.适用场景

  在以下情况下可以考虑使用原型模式:

  1. 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  2. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java设计模式-原型模式 的相关文章

随机推荐

  • 人脸识别刷脸以往大多应用在安防领域

    最近你会看到各大媒体 网络平台都是关于刷脸的报道 感觉不聊聊刷脸是不是就已经跟这个时代脱节了 简单的说 刷脸支付是一种连手机都不需要的新型支付方式 刷脸支付意味着在手机没电 信号不好 因为卡顿打不开支付宝和微信 甚至出门忘记带手机时也能完成
  • AMBA低功耗接口(一)Q_Channel

    AMBA提供了 低功耗的接口 用于实现power控制功能 目前 AMBA里面 包含2种低功耗接口 Q Channel 实现简单的power控制 如上电 下电 P Channel 实现复杂的power控制 如全上电 半上电 1 4上电等 AR
  • 微服务springcloud环境下基于Netty搭建websocket集群实现服务器消息推送----netty是yyds

    netty搭建websocket 1 背景 2 websocket 3 netty 3 1 socket 3 2 Java IO模型 3 3 netty 3 3 1 概念 3 3 2 三大特点 3 3 3 主从Reactor架构图 3 3
  • 期货基础知识

    一 期货是什么 期货是与现货相对应 并由现货衍生而来 期货通常指期货合约 期货与现货完全不同 现货是实实在在可以交易的货 商品 期货主要不是货 而是以某种大众产品如棉花 大豆 石油等及金融资产如股票 债券等为标的标准化可交易合约 因此 这个
  • C++11下的单例模式

    动机 在软件系统中 经常有这样一些特殊的类 必须保证它们在系统中只存在一个实例 才能确保它们的逻辑正确性 以及良好的效率 绕过常规的构造器 提供一种机制来保证一个类只有一个实例 定义 保证一个类仅有一个实例 并提供一个该实例的全局访问点 1
  • Java 反射小案例(使用配置文件)

    Java 反射小案例 使用配置文件 记录学习过程 定义两个类一个Student 一个Person 还有一个 主类Reflect Test 再加一个配置文件 两类 public class Student public void sleep
  • RabbitMQ死信队列学习笔记

    视频地址 死信的概念 先从概念解释上搞清楚这个定义 死信 顾名思义就是无法被消费的消息 字面意思可以这样理解 一般来说 producer 将消息投递到 broker 或者直接到 queue 里了 consumer 从 queue 取出消息进
  • JDBC连接

    JDBC连接 加载JDBC驱动程序 打开JDBC连接 打开带有URL的连接 使用URL 用户和密码打开连接 使用URL和属性打开连接 关闭JDBC连接 通过Try With Resources关闭连接 setAutoCommit commi
  • 在机器学习中,你需要多少训练数据?

    你为什么会问这个问题 首先我们要搞清楚你为什么会问需要多大的训练数据集 可能你现在有以下情况 你有太多的数据 可以考虑通过构建学习曲线 learning curves 来预估样本数据集 representative sample 的大小或者
  • 焊缝识别技术研究

    一 绪论 提高焊接效率和保证焊接质量是焊接产业行业的重要发展方向 示教 在线 的编程模式需要占用大量的编程时间 效率低下 同时 示教的精度难以保证 可重复性差 难以适用于复杂的机器人作业 离线编程软件有RobotMaster RobotSt
  • JS判断对象、数组是否包含某个属性、某个值

    一 判断对象是否包含某个属性 可以使用以下几种方法来判断对象是否包含某个属性 1 in 操作符 使用 in 操作符可以检查对象是否包含指定的属性 它会检查对象及其原型链上的所有属性 const obj name John age 25 co
  • 2023智源大会议程公开丨视觉与多模态大模型论坛

    6月9日 2023北京智源大会 将邀请AI领域的探索者 实践者 以及关心智能科学的每个人 共同拉开未来舞台的帷幕 你准备好了吗 与会知名嘉宾包括 图灵奖得主Yann LeCun OpenAI创始人Sam Altman 图灵奖得主Geoffr
  • 第十六篇:关于Unity开发WebGL遇到的坑

    1 资源消耗问题 Unity开发Web 项目 发布出来的项目是很消耗内存资源的 这是Unity引擎决定 我们要做的就是尽可能的优化 通常的方法有降低模型面数 降低贴图的大小 减少点光源 减小烘焙贴图大小等 当然也可以使用three js开发
  • 机器人流程自动化(RPA):6大开源工具

    51CTO com快译 与实施许多新软件一样 开始使用机器人流程自动化 RPA 方面面临自建还是外购的选择 如果是自建 只要你有合适的人员 预算到位 可以从头开始编写自己的机器人 如果是外购 有一个新兴的商业软件供应商市场 它们提供各式各样
  • 数据链路层--以太网协议

    目录 以太网协议 认识以太网 以太网帧格式 认识MAC地址 认识MTU 数据跨网络传输的过程 ARP协议 ARP概念 ARP数据的格式 ARP协议的工作流程 ARP请求的过程 ARP应答的过程 ARP缓存表 在学习完ip协议后知道 网络层解
  • 大话设计模式学习 C++

    工厂模式 include bits stdc h using namespace std 基类 进行操作 class Operation protected data double numberA 0 double numberB 0 pu
  • SSM项目可以正常启动并访问,控制台无报错,但是项目调用controller层返回404

  • 18650锂电池保护板接线图_锂电池保护板几种接线方法介绍 - 全文

    锂电池保护板简介 锂电池保护板是对串联锂电池组的充放电保护 在充满电时能保证各单体电池之间的电压差异小于设定值 一般 20mV 实现电池组各单体电池的均充 有效地改善了串联充电方式下的充电效果 同时检测电池组中各个单体电池的过压 欠压 过流
  • Linux C++ 遍历子目录下的文件名称并保存到变量vector<string> 中

    遍历子目录下的文件名称并保存到变量vector 中 include
  • Java设计模式-原型模式

    原型模式 在有些系统中 存在大量相同或相似对象的创建问题 如果用传统的构造函数来创建对象 会比较复杂且耗时耗资源 用原型模式生成对象就很高效 原型模式的定义与特点 原型 Prototype 模式的定义如下 用一个已经创建的实例作为原型 通过