当前位置: 首页 > news >正文

设计模式 - 原型模式

原型模式(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”关键字所触发的实例化操作。

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

-- 原型

相关文章:

  • LTC3130EMSE#TRPBF ADI电子元器件深度解析 物联网/工业传感器首选!
  • RK3568笔记八十五:LVGL播放AVI视频
  • 单服务器部署多个Discuz! X3.5站点并独立Redis配置方案
  • 蜻蜓Q系统的技术演进:从Laravel 6到Laravel 8的升级之路-优雅草卓伊凡
  • NAT基础配置实验
  • Java求职者面试指南:Spring, Spring Boot, Spring MVC, MyBatis技术点深度解析
  • FPGA基础 -- Verilog 概率分布函数
  • 图床项目--展示
  • Linux简单了解以及VM虚拟机的安装使用(后端程序员)
  • 探索算法秘境:量子随机游走算法及其在图论问题中的创新应用
  • Java使用itext pdf生成PDF文档
  • HarmonyOS-ArkTS开发指南:从基础到实战
  • (17)-java+ selenium->自动化测试-元素定位大法之By css上
  • StableDiffusion实战-手机壁纸制作 第一篇:从零基础到生成艺术品的第一步!
  • 前端HTML学习笔记
  • Java 语言的优势场景:企业级应用与新兴领域的首选
  • GRBL_UNO R3编译下载
  • 不会PLC,怎么学上位机?
  • 基于keepalived、vip实现高可用nginx (centos)
  • 基于Python的机动车辆推荐及预测分析系统
  • 电商网站开发工程师/atp最新排名
  • 有什么网站做任务换q币吗/百度广告优化师
  • 软件园二期做网站的公司有哪些/网站如何优化推广
  • 山西太原网站制作/seo建站的步骤
  • 湛江网站建设制作费用/武汉网站seo推广
  • 贵州专业网站建设公司哪家好/查询域名注册信息