news 2026/10/4 1:48:13

深入理解 Java 设计模式之原型模式(Prototype):以 java-design-patterns 仓库为例掌握对象克隆的高效实例化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Java 设计模式之原型模式(Prototype):以 java-design-patterns 仓库为例掌握对象克隆的高效实例化
  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

原型模式(Prototype Pattern)是 GoF 四大创建型设计模式之一,其核心思想是"通过克隆一个原型实例来创建新对象",而非每次都用new关键字从头构造。本文以 java-design-patterns 仓库中的 prototype 模块 为完整案例,系统讲解原型模式在 Java 中的推荐实现方式、源码级调用链、适用场景与权衡取舍,帮助你在实际项目中用克隆机制轻松复制复杂对象、降低创建成本。

模式定位:Clone 的意图与别名

在 java-design-patterns 仓库中,原型模式归属于创建型(Creational)类别,其别名即Clone。模式的意图非常明确:

使用一个原型实例来指定要创建的对象种类,并通过克隆该原型实例来创建新实例。

这与仓库中 App.java 的类注释相互印证:原型模式用于"当待创建对象的类型由一个原型实例决定时,克隆该原型以产生新对象",其目标是避免在客户端应用中为每种产品都编写对应的创建器子类(如抽象工厂模式那样),同时规避用new关键字创建对象所带来的固有开销。

现实世界类比

设想一家定制家具公司:它不会在每次下单时从头制作一件家具,而是为最受欢迎的款式保留"原型"。客户下单某一设计时,公司直接克隆对应原型并做必要定制。因为基础结构与设计细节已经就位,公司能以一致的品质快速交付订单——这正是软件世界中"原型 + 克隆"机制的直观写照。

用一句话概括就是:基于一个已有对象,通过克隆来创建新对象。Wikipedia 对此的定义同样简洁:原型模式是一种创建型设计模式,当待创建对象的类型由一个原型实例决定时,通过克隆该原型来产生新对象。

模式结构总览:序列图

如上图所示,原型模式的典型交互流程是:客户端向原型对象发起"复制"请求,原型通过克隆机制返回一个状态相同的新实例,客户端随后可对新实例做进一步定制,整个过程对客户端隐藏了对象构造的内部复杂性。

Java 实现示例:从 Prototype 基类到具体原型

在 Java 中,原型模式的推荐做法是:先定义一个带克隆方法的接口或抽象基类,再由具体原型类继承/实现它。本仓库的实现正是这一思路的范本。

第一步:定义原型基类

public abstract class Prototype<T> implements Cloneable { @SneakyThrows public T copy() { return (T) super.clone(); } }

对应源码见 Prototype.java(第 32–39 行)。要点解读:

  • 基类实现了Cloneable接口——这是 Java 原生Object.clone()能够正常执行而不抛CloneNotSupportedException的前提;
  • copy()内部直接调用super.clone(),即Object.clone()的浅拷贝语义,通过泛型T把返回类型收窄到具体原型类型,调用方无需再强转;
  • @SneakyThrows(Lombok 注解)把CloneNotSupportedException的声明吞掉,让克隆调用代码更简洁。源码注释还明确指出:该方法返回"本对象的浅拷贝;若对象不可克隆则返回 null"。

第二步:建立产品类层级

示例包含一组"生物"(creature)类层级。以Beast(野兽)与OrcBeast(兽人野兽)为例:

@EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Beast extends Prototype<Beast> { public Beast(Beast source) {} }
@EqualsAndHashCode(callSuper = false) @RequiredArgsConstructor public class OrcBeast extends Beast { private final String weapon; public OrcBeast(OrcBeast orcBeast) { super(orcBeast); this.weapon = orcBeast.weapon; } @Override public String toString() { return "Orcish wolf attacks with " + weapon; } }

对应源码见 Beast.java 与 OrcBeast.java。可以观察到仓库实现的两个设计细节:

  • 抽象基类持有两种构造方式:@NoArgsConstructor生成的无参构造供"从零创建原型"使用;带source参数的拷贝构造供"由已有原型复制字段"使用;
  • 具体类提供同名拷贝构造:OrcBeast(OrcBeast orcBeast)显式地把weapon字段从源对象复制到新对象。虽然本示例的copy()走的是Object.clone()浅拷贝,但这些拷贝构造展示了另一种实现克隆的常见路径——当克隆逻辑需要深度复制或额外定制时,可以改由拷贝构造手工完成。

完整的类层级还包括Mage(法师)与Warlord( warlord,统帅/战将),并有精灵(Elf)与兽人(Orc)两个种族的具体实现:ElfMage、OrcMage、ElfWarlord、OrcWarlord、ElfBeast、OrcBeast。其中:

  • ElfMage持有helpType字段,toString()输出形如"Elven mage helps in cooking";
  • OrcMage、OrcWarlord持有weapon字段,输出形如"Orcish mage attacks with axe";
  • ElfBeast输出"Elven eagle helps in ...",OrcBeast输出"Orcish wolf attacks with ..."。

以ElfMage为例,ElfMage.java 同样采用"@RequiredArgsConstructor生成带参构造 + 手工拷贝构造复制 helpType"的组合模式。三个抽象基类 Mage.java、Warlord.java、Beast.java 结构完全对称,都继承自Prototype<T>。

第三步:用工厂对客户端隐藏克隆细节

要充分发挥原型模式的价值,还需要一个"生产不同种类生物"的工厂。仓库提供了HeroFactory接口与HeroFactoryImpl实现:

public interface HeroFactory { Mage createMage(); Warlord createWarlord(); Beast createBeast(); }
@RequiredArgsConstructor public class HeroFactoryImpl implements HeroFactory { private final Mage mage; private final Warlord warlord; private final Beast beast; public Mage createMage() { return mage.copy(); } public Warlord createWarlord() { return warlord.copy(); } public Beast createBeast() { return beast.copy(); } }

对应源码见 HeroFactory.java 与 HeroFactoryImpl.java。这里的设计值得留意:

  • 原型以构造参数注入:@RequiredArgsConstructor让工厂在构造时接收三个原型实例(一个 Mage、一个 Warlord、一个 Beast),工厂本身不关心具体种族,只负责"复制原型";
  • 创建方法全部委托给copy():createMage()返回mage.copy(),其余方法同理。这正印证了 App.java 类注释中的描述——"工厂的原型对象以构造参数形式给出"。

第四步:完整运行示例

App.java 的main方法展示了完整的实战流程:

public static void main(String[] args) { var factory = new HeroFactoryImpl( new ElfMage("cooking"), new ElfWarlord("cleaning"), new ElfBeast("protecting") ); var mage = factory.createMage(); var warlord = factory.createWarlord(); var beast = factory.createBeast(); LOGGER.info(mage.toString()); LOGGER.info(warlord.toString()); LOGGER.info(beast.toString()); factory = new HeroFactoryImpl( new OrcMage("axe"), new OrcWarlord("sword"), new OrcBeast("laser") ); mage = factory.createMage(); warlord = factory.createWarlord(); beast = factory.createBeast(); LOGGER.info(mage.toString()); LOGGER.info(warlord.toString()); LOGGER.info(beast.toString()); }

运行该示例,控制台输出如下:

08:36:19.012 [main] INFO com.iluwatar.prototype.App -- Elven mage helps in cooking 08:36:19.013 [main] INFO com.iluwatar.prototype.App -- Elven warlord helps in cleaning 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Elven eagle helps in protecting 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish mage attacks with axe 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish warlord attacks with sword 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish wolf attacks with laser

从输出可以看到:两次调用完全相同的createMage()/createWarlord()/createBeast(),却分别产出了精灵族与兽人族的不同实例——区别仅在于注入工厂的原型对象不同。这正是原型模式"由原型实例决定创建类型"的直观体现。

测试如何验证克隆语义

仓库还提供了针对性的单元测试,见 PrototypeTest.java。测试通过参数化方式覆盖全部 6 个具体原型类,每个用例断言三件事:

  1. toString()输出与期望一致(如new OrcBeast("axe")对应"Orcish wolf attacks with axe");
  2. copy()返回的克隆对象assertNotNull(非空);
  3. 克隆对象与原对象assertNotSame(不是同一个引用,即确实产生了新实例)。

其中"assertNotSame"正是验证原型模式核心语义的关键:copy()必须返回一个全新的对象实例,而非原对象的引用。测试数据同时印证了类结构与toString()行为——例如ElfBeast("cooking")输出"Elven eagle helps in cooking"。

何时使用原型模式

根据仓库文档并结合模式特性,以下场景适合引入原型模式:

  • 待实例化的类在运行时才确定,例如通过动态加载(dynamic loading)方式获得具体类;
  • 希望避免建立与产品类层级平行的工厂类层级——原型模式用"注入原型 + 克隆"替代了每个产品对应一个工厂子类的做法;
  • 类的实例只存在少数几种状态组合:更便捷的做法是预先安装相应数量的原型,需要时直接克隆,而不是每次手工new并设置状态;
  • 对象创建成本远高于克隆成本:当构造过程涉及昂贵的初始化(数据库连接、IO、复杂计算)时,克隆已有实例能显著节省开销;
  • 在运行时才知道要实例化的具体类,客户端不应直接依赖具体类名。

真实世界应用

原型模式在 Java 生态与工业界有广泛实践:

  • Java 原生Object.clone()方法是原型模式的经典实现(本仓库的Prototype.copy()正是对它的一层薄封装);
  • GUI 库常用原型来创建按钮、窗口、控件等构件,通过克隆模板控件快速生成同类组件;
  • 游戏开发中创建多个属性相近的对象(例如大批量生成属性相似的敌人角色)时,克隆比逐字段构造更高效。

优点与权衡

优点

  • 隐藏实例化新对象的复杂性:客户端只需调用copy(),无需了解构造细节;
  • 减少类的数量:不需要为每个产品类型单独编写工厂子类;
  • 支持运行时动态增删对象:原型集合可随运行状态扩展,新增一种原型即可产出新类型对象。

权衡

  • 需要实现克隆机制,可能较为复杂:Cloneable的浅拷贝语义容易踩坑,字段为引用类型时需格外小心;
  • 深拷贝实现难度高:尤其当类拥有复杂对象图、存在循环引用时,正确实现深克隆很困难(本仓库示例走浅拷贝路径,正是为了保持示例的简洁)。

与相关模式的关联

  • Abstract Factory(抽象工厂):两者都涉及对象创建,但原型模式通过克隆原型实例创建对象,抽象工厂则通过工厂方法创建对象;
  • Singleton(单例):单例若允许克隆其唯一实例,也可借助原型模式来产生新实例;
  • Composite(组合):原型常被用于组合结构中,以支持组件树的动态创建。

小结

原型模式是创建型模式家族中最"经济"的一员:当对象构造昂贵、类型在运行时才确定、或希望避免工厂类层级膨胀时,克隆一个原型即可获得全新实例。java-design-patterns 仓库的 prototype 模块 用"抽象基类Prototype<T>+ 具体原型 + 原型工厂HeroFactoryImpl"三层结构,完整展示了从克隆基类到运行时产出不同种族生物的端到端实现,配合 PrototypeTest.java 中"克隆结果非同一引用"的断言,帮助你准确理解并正确落地这一模式。若需深入对比,可继续研读仓库中 abstract-factory、singleton 与 composite 模块的实现。

  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

相关推荐

上一篇:Shairport Sync中的日志聚合查询权限控制:实现
下一篇:深度解析Obsidian Web Clipper:高效构建个人知识管理系统的浏览器扩展架构

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 1:47:18

Monocle拟时序分析:为何必须用原始counts而非SCT或整合数据?

上个月处理一批神经元分化的10x数据时&#xff0c;同组的师妹跑过来问我&#xff1a;Monocle做拟时序分析&#xff0c;到底该喂SCT整合后的数据&#xff0c;还是老老实实用RNA assay里的counts&#xff1f;她说自己用Seurat做了SCT整合&#xff0c;又用Harmony跑了integration&…

作者头像 李华
网站建设 2026/10/4 1:46:15

Linux下C语言真实执行机制:编译、内存、调试全链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华