- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
原型模式(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 个具体原型类,每个用例断言三件事:
toString()输出与期望一致(如new OrcBeast("axe")对应"Orcish wolf attacks with axe");copy()返回的克隆对象assertNotNull(非空);- 克隆对象与原对象
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
相关推荐
Java 原型模式(Prototype Pattern)深入解析:基于 java-design-patterns 仓库的克隆式对象创建实战
Java 原型模式(Prototype Pattern)深入解析:基于 java design patterns 仓库的克隆式对象创建实战 导读 原型模式(Pr
示例工程教程深入解析 Java 设计模式之扩展对象模式(Extension Objects Pattern):以 java-design-patterns 仓库为实战范本
深入解析 Java 设计模式之扩展对象模式(Extension Objects Pattern):以 java design patterns 仓库为实战范本
示例工程教程深入解析 Java 设计模式之享元模式(Flyweight):以 java-design-patterns 仓库的炼金术商店为例
深入解析 Java 设计模式之享元模式(Flyweight):以 java design patterns 仓库的炼金术商店为例 享元模式(Flyweight)
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考