原型模式(Prototype),在制造业种通常是指大批量生产开始之前研发出的概念模型,并基于各种参数指标对其进行检验,效果达到了质量要求,即可参照这个原型进行批量生产。即,原型模式可以用对象创建对象,而不是用类创建对象,以此达到效率的提升。

举个栗子,类似于打印机和复印机的区别:

  • 第一份打印出来的原文稿,我们称之为“原型文件”
  • 对于复印过程,我们称之为“原型拷贝”

原型模式对于 非常复杂初始化过程的对象,或者是 需要消耗大量资源 的情况下,原型模式是更好的选择。

目录

一、以空战游戏为例

1. 敌机类 EnemyPlane 代码:

2. 怎样创建500台敌机

1)  for循环批量生产敌机

2)  懒加载依然有性能问题

3)  细胞分裂

3. 复杂对象的克隆 - 深拷贝、浅拷贝

4. 克隆的本质


一、以空战游戏为例

假设我们设计一个空战游戏的程序,

为了简单,我们设定游戏为单打,也就是说主角飞机只有一驾,而敌机有很多驾,而且可以在屏幕上垂直向下移动来撞击注解飞机,具体是怎样实现的呢?其实比较简单,就是程序不停改变坐标并且在画面上重绘而已。由浅入深,我们 先试着写一个敌机类

Tips:空战游戏中的主角如果是单个实例的话,其实就用到单例模式了。可以参考: 设计模式 - 单例模式-CSDN博客,本文只关注可以有多个实例的敌机。

1. 敌机类 EnemyPlane 代码:

public class EnemyPlane {private int x; // 敌机横坐标private int y = 0; // 敌机纵坐标public EnemyPlane(int x) { // 构造器this.x = x;}public int getX() {return x;}public int getY() {return y;}public void fly() { // 让敌机飞y++; // 每调用一次,敌机飞行时纵坐标+1}
}

纵坐标固定为0,由于敌机一开始是从顶部飞出去的。

只有getter没有setter,也就是只能在初始化时确定好敌机的横坐标x,之后不允许改了


2. 怎样创建500台敌机

我们想让敌机向雨点一样不断下落,首先需要实例化500驾敌机。

1)  for循环批量生产敌机

这样做法看似没有问题,实际上效率非常低。

游戏画面不可能同时出现500驾敌机,而且在游戏未开始的时候就加载了500驾,不仅使加载速度变慢、也是对有限内存资源的一种浪费

public class Client {public static void main(String[] args) {List<EnemyPlane> enemyPlanes = new ArrayList<EnemyPlane>();for (int i = 0; i < 500; i++) {// 此处于随机纵坐标处出现敌机EnemyPlane ep = new EnemyPlane(new Random().nextInt(200));enemyPlanes.add(ep);}}
}

那么,到底什么时候才去构造敌机,-- 当然是懒加载了

按照地图坐标,屏幕滚动到某一点时才实时构造敌机,就解决问题了。

2)  懒加载依然有性能问题

主要原因在于,“new” 关键字进行的基于类的实例化过程,每驾敌机都进行全新构造的做法是不合适的,其代价是耗费更多的CPU资源。

尤其大型游戏中,很多个线程不停运转着,CPU资源本身就非常宝贵,此时如果进行大量的类构造与复杂的初始化工作,必然会造成游戏卡顿、甚至会造成系统无响应

3)  细胞分裂

硬件永远离不开优秀的软件,我们绝不允许以糟糕的软件设计对硬件发起挑战。

既然循环第一次之后已经实例化好了一个敌机原型,那么之后又何必去重复这个构造过程呢?敌机对象是否能像细胞分裂一样自我复制呢?要解决这个问题,原型模式是最好的解决方案了。

1)重构敌机类,支持原型拷贝

让敌机类EnemyPlane实现了java.lang包中的克隆接口Cloneable,并在实现方法中调用了父类Object的克隆方法,省去了由类而生的再造过程。

public class EnemyPlane implements Cloneable {private int x; // 敌机横坐标private int y = 0; // 敌机纵坐标public EnemyPlane(int x) { // 构造器this.x = x;}public int getX() {return x;}public int getY() {return y;}public void fly() { // 让敌机飞y++; // 每调用一次,敌机飞行时纵坐标+1}// 此处开放setX,是为了让克隆后的实例重新修改横坐标public void setX(int x) {this.x = x;}// 重写克隆方法@Overridepublic EnemyPlane clone() throws CloneNotSupportedException {return (EnemyPlane)super.clone();}
}

至此,克隆模式其实已经实现了,只需简单的调用克隆方法即可更高效地得到一个全新的实例副本。

为了更方便的生产飞机,我们决定定义一个敌机克隆工厂类

public class EnemyPlaneFactory {// 此处用单例模式创建一个敌机原型private static EnemyPlane protoType = new EnemyPlane(200);// 获取敌机克隆实例public static EnemyPlane getInstance(int x) {EnemyPlane clone = protoType.clone(); // 复制原型机clone.setX(x); // 重新设置克隆机的x坐标return clone;}
}

我们在敌机克隆工厂类EnemyPlaneFactory中第4行使用了一个静态的敌机对象作为原型,其中获取敌机实例的方法getInstance(),其简单的调用克隆方法得到了一个新的克隆对象(此处省略了一场捕获代码),并将其横坐标重设为传入的参数,最后返回此克隆对象,这样我们便可以轻松获取一驾敌机的克隆实例了

敌机克隆工厂类定义完毕,客户端代码就留给读者自己实践了。

但需要注意,一定要使用懒加载方式,如此既可以节省内存空间,又可以确保敌机的实例化速度,实现敌机的即时性按需克隆,这样游戏便再也不会出现卡顿现象了。


3. 复杂对象的克隆 - 深拷贝、浅拷贝

最后,在使用原型模式之前,必须搞清楚深拷贝与浅拷贝这两个概念,否则会对复杂对象的克隆感到无比困惑

假设,敌机类里有一颗子弹可以发射并击杀玩家的飞机,那么敌机中则包含一颗实例化好的子弹对象,请参考代码清单:

public class EnemyPlane implements Cloneable {private Bullet bullet = new Bullet();private int x; // 敌机横坐标private int y = 0; // 敌机纵坐标// 之后代码省略……
}

如上代码,此时如果进行克隆操作,能否将子弹对象一起成功克隆呢?

答案是否定的:

  • Java中的变量分为原始类型和引用类型,浅拷贝指只复制原始类型的值。而引用类型也会被拷贝,但是这个操作知识拷贝了引用类型的地址引用(指针),也就是说副本敌机与原型敌机中的子弹是同一颗,因为两个同样的地址实际指向的内存对象是同一个bullet对象。
  • 需要注意的是,克隆方法中调用父类Objecrt的clone方法进行的是浅拷贝,所以此处的bullet并没有真正克隆。
public class EnemyPlane implements Cloneable {private Bullet bullet;private int x; // 敌机横坐标private int y = 0; // 敌机纵坐标public EnemyPlane(int x, Bullet bullet) {this.x = x;this.bullet = bullet;}@Overrideprotected EnemyPlane clone() throws CloneNotSupportedException {EnemyPlane clonePlane = (EnemyPlane) super.clone(); // 克隆出敌机clonePlane.setBullet(this.bullet.clone()); // 对子弹进行深拷贝return clonePlane;}// 之后代码省略……
}

如上代码显示,首先clone方法中依旧对敌机对象进行克隆操作,紧接着对敌机子弹bullet也进行了克隆,这个就是深拷贝操作。当然,此处要注意对于子弹类Bullet同样也得实现克隆接口,请读者自行实现,此处就不再赘述了。

简而言之:深拷贝会复制对象及其所有嵌套子对象,而浅拷贝只复制对象本身,嵌套子对象仍然引用原对象。


4. 克隆的本质

在使用克隆模式对游戏代码反复重构后,游戏性能得到了极大的提升,流畅的游戏画面确保了优秀的用户体验。最后,我们来看原型模式的类结构。

  • Prototype(原型接口)​:声明克隆方法,对应本例程代码中的Cloneable接口。
  • ConcretePrototype(原型实现)​:原型接口的实现类,实现方法中调用super. clone()即可得到新克隆的对象。
  • Client(客户端)​:客户端只需调用实现此接口的原型对象方法clone(),便可轻松地得到一个全新的实例对象。

从类到对象叫作“创建”​,而由本体对象至副本对象则叫作“克隆”​,当需要创建多个类似的复杂对象时,我们就可以考虑用原型模式。

究其本质,克隆操作时Java虚拟机会进行内存操作,直接拷贝原型对象数据流生成新的副本对象,绝不会拖泥带水触发一些多余的复杂操作(如类加载、实例化、初始化等),所以其效率远远高于“new”关键字所触发的实例化操作。

-- 秒懂设计模式学习笔记

-- 原型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87748.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87748.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/87748.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL数据库基础:从零开始的第一步【Linux】

前言 各位小伙伴们&#xff0c;好久不见&#xff01;近期&#xff0c;我的文章更新频率确实有些缓慢&#xff0c;在此诚挚地向大家道歉。这个月是我的期末考试月&#xff0c;正处于紧张的复习&#xff08;也可以说是重新学习&#xff09;阶段。尽管学业繁忙&#xff0c;但我依然…

502 Bad Gateway:服务器作为网关或代理时收到无效响应处理方式

502 Bad Gateway 错误是 Web 开发和服务器管理中常见的问题&#xff0c;通常表示网关或代理服务器收到无效响应。这种错误可能由多种原因引起&#xff0c;包括后端服务故障、网络问题或配置错误等。了解502错误的原因及其处理方式&#xff0c;对于维护网站的可用性和用户体验至…

Abel 变换,离散型分部积分

文章目录 零、引入&#xff1a;分部积分一、Abel 变换1.1 Abel 变换1.2 证明 二、一些比较浅显的应用2.1 等差 乘 等比型求和2.2 平方求和公式2.3 不等式证明 三、一些算法题的式子优化3.1 3500.将数组分割为子数组的最小代价3.2 D. Array Splitting3.3 300. 任务安排1 零、引入…

火山 RTC 引擎12----合流转推 集成

一、火山、网易 合流转推集成 1、 首次先要startPush,要不然,推不了流 void NRTCEngine::PushToCDN(std::string taskID, std::string url) {if (m_video == nullptr) return;bytertc::IMixedStreamConfig* config = getMixedStreamConfig(url);int ret = m_video->star…

基于STM32设计的物联网疫苗冷链物流监测系统

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成【4】设计意义【5】国内外研究现状(1)国内研究现状(2)国外研究现状(3)技术演进趋势分析(4)现存技术缺口(5)关键案例技术对比表【6】摘要1.2 设计思路1.3 系统功能总结1.4 开…

音频中采样率和帧是什么?怎么理解?

视频中的“帧”是指一张图片&#xff0c;那么在音频中&#xff0c;“帧”的含义就完全不同了。理解音频中的“帧”概念&#xff0c;对做音视频处理、流媒体开发非常关键。 一、声音是怎么采集的&#xff1f; 音频采集是指通过麦克风等设备捕捉周围环境中的声波&#xff0c;并…

第三方检测护航软件登记:企业合规的技术通行证与市场信任基石

一、软件产品登记测试&#xff1a;合规化的必经之路 根据《软件产品管理办法》&#xff0c;所有上市软件必须通过第三方检测机构的专业评估&#xff0c;确保功能、性能、安全性等指标符合国家标准&#xff08;如GB/T 25000系列&#xff09;。这一强制性要求不仅规避了法律风险…

产品页不被收录的6个技术原因(非重复内容/爬虫限制类)

页面未被收录的原因可能藏在代码架构或服务器配置中 比如爬虫无法“看懂”你的动态内容&#xff0c;或是某个参数设置错误导致页面被判定为重复。 本文从技术排查角度出发&#xff0c;整理6个最易被忽视但直接影响收录的实操问题。 页面加载速度拖慢爬虫抓取 例如&#xff0…

如何在FastAPI中打造一个既安全又灵活的权限管理系统?

title: 如何在FastAPI中打造一个既安全又灵活的权限管理系统? date: 2025/06/16 08:17:05 updated: 2025/06/16 08:17:05 author: cmdragon excerpt: FastAPI权限系统通过依赖注入实现三级验证:身份认证、角色验证和权限校验。数据库模型包括用户、角色和权限注册表,支持…

通过Radius认证服务器实现飞塔/华为防火墙二次认证:原理、实践与安全价值解析

引言&#xff1a;数字化转型中的身份认证挑战 在数字化转型加速的今天&#xff0c;企业网络边界日益模糊&#xff0c;混合云架构、远程办公、物联网设备接入等场景对网络安全提出全新挑战。传统防火墙基于IP/端口的访问控制已无法满足动态安全需求&#xff0c;如何构建"持…

golang--context的使用指南与核心特性

Go 语言 context 包&#xff1a;使用指南与核心特性 一、context 的本质与设计目的 context 是 Go 语言中管理请求生命周期的核心机制&#xff0c;它提供了一套统一的方式来&#xff1a; 传递请求范围数据&#xff08;如用户认证信息&#xff09;控制跨 goroutine 的生命周期…

耗时3小时,把这两天做好的爬虫程序,用Python封装成exe文件

先执行命令如下&#xff1a; pip install pyinstaller py -m PyInstaller --log-levelDEBUG --add-data "config.ini;." nmpa_gui.py很快在dist目录下就有生成一个nmpa_gui文件夹&#xff0c;运行 nmpa_gui.exe&#xff0c;报错&#xff1a; 1️⃣初始化爬虫… 程序…

Linux下nginx访问路径页面

第一步&#xff1a;通过Xshell在虚拟机中下载nginx sudo apt-get install nginx 第二步&#xff1a;进入nginx配置页面 cd /etc/nginx 我这里创建了一个html文件夹 在进入去创建页面并且重新加载 boahuboahu-VMware-Virtual-Platform:/$ cd /etc/nginx boahuboahu-VMware-Vir…

三维视频融合怎么弄?三步实现精准投射与自由修剪

分享大纲&#xff1a; 1、场景引入&#xff1a;为什么你的三维场景视频融合效果不理想&#xff1f; 2、解决方案&#xff1a;捷码视频融合三步操作指南 3、捷码平台&#xff1a;低代码构建动态三维视界 在智慧城市中的安防领域&#xff0c;将实时视频与三维场景融合已是大势需求…

探索阿里云网络与CDN产品:解锁高效网络体验

阿里云网络产品概述 在云计算蓬勃发展的当下&#xff0c;网络作为连接计算、存储与用户的关键纽带&#xff0c;其重要性不言而喻。阿里云作为全球知名的云计算服务提供商&#xff0c;凭借其丰富且强大的网络产品体系&#xff0c;为企业数字化转型筑牢了坚实的网络根基&#xf…

深入理解C语言指针(二):从数组到多级指针的全面解析

作为C语言的核心概念&#xff0c;指针常常让初学者感到困惑。本文将从数组与指针的关系入手&#xff0c;逐步揭开指针在数组操作、函数传参以及多级指针中的神秘面纱&#xff0c;帮助你建立系统的指针知识体系。 一、数组名的双重身份&#xff1a;首地址与整体标识 在C语言中&a…

Windows PPT/word怎么pdf不降低分辨率,插入可编辑

Windows PPT/word怎么pdf不降低分辨率 下载软件Inkscape&#xff1a;Inkscape - Draw Freely. | Inkscape 然后使用Inkscape将你的PDF转为svg, 然后用office的PPT打开&#xff0c;将svg复制进PPT/word&#xff0c;然后保存就可以了 插入可编辑的&#xff08;只能通过Mac的才可…

vue3 select 选中值时,即获得id,也获得name值并且输入框正确选中

1.获取 name和id 直接绑定对象 将 value 绑定为整个对象&#xff0c;通过 change 事件获取完整数据 value-key 绑定唯一标识 value 绑定为整个对象&#xff0c;通过 change 事件获取完整数据 <el-select v-model"selectedItem" change"handleChange"…

什么是Seata

Seata的实现原理主要围绕其核心架构&#xff08;TC/TM/RM&#xff09;和事务模式&#xff08;如AT、TCC等&#xff09;展开&#xff0c;通过协调全局事务与分支事务的协作保证数据一致性。以下是核心实现原理的详细解析&#xff1a; ⚙️ ​​一、核心架构协作机制​​ Seata通…

linux ARM64架构用户空间和内核空间的区分

一、ARM64 架构地址空间的「黄金分割」 ARM64&#xff08;ARMv8-A&#xff09;采用 48 位虚拟地址&#xff08;Linux 默认配置&#xff09;&#xff0c;总空间为 256TB&#xff0c;分为高低两个 128TB 区域&#xff1a; 1. 地址空间整体布局 虚拟地址空间&#xff08;48位&a…