Java设计模式精讲---04原型模式

你是否遇到过这样的场景:需要创建大量结构相似、仅少数属性不同的对象(比如 100 个游戏角色,基础属性相同,仅昵称和装备有差异)?如果每次都通过new关键字从头初始化,不仅代码冗余,还会因为重复执行构造函数中的复杂逻辑(比如数据库查询、网络请求)浪费性能。

这时候,原型模式(Prototype Pattern) 就能派上用场 ------ 它的核心思想是:通过复制现有对象(原型)来创建新对象,而非重新初始化,就像现实中 "复制粘贴" 文档再修改细节,既高效又省力。

一、核心概念:克隆,而非新建

原型模式的本质是 "对象克隆",它通过让对象自身提供复制方法,将对象的创建过程从 "主动新建" 转为 "被动复制"。关键角色只有两个:

  • 原型接口(Prototype) :定义克隆方法(比如 Java 中的Cloneable接口,虽然是标记接口,但约定了clone()方法);
  • 具体原型(Concrete Prototype):实现克隆方法,完成自身复制。
二、实现方式:浅克隆 vs 深克隆

克隆的核心是 "复制对象的属性",但根据属性类型(基本类型 / 引用类型),克隆分为两种:

1. 浅克隆:只复制 "表面"

浅克隆会复制对象的基本类型属性(int、String 等),但对于引用类型属性(比如对象、集合),仅复制引用地址 ------ 新对象和原对象的引用属性会指向同一个内存地址,修改其中一个会影响另一个。

Java 实现示例 (基于Cloneable接口):

java 复制代码
​
// 具体原型:游戏角色
class GameRole implements Cloneable {
    private String name; // 基本类型
    private int level;   // 基本类型
    private Equipment equipment; // 引用类型(装备)

    // 构造函数(模拟复杂初始化逻辑)
    public GameRole(String name, int level, Equipment equipment) {
        this.name = name;
        this.level = level;
        this.equipment = equipment;
        System.out.println("执行复杂初始化(比如加载角色模型)...");
    }

    // 实现克隆方法(浅克隆)
    @Override
    protected GameRole clone() throws CloneNotSupportedException {
        return (GameRole) super.clone(); // Object.clone()默认是浅克隆
    }

    // getter/setter省略
}

// 引用类型:装备
class Equipment {
    private String weapon;
    public Equipment(String weapon) { this.weapon = weapon; }
    // getter/setter省略
}

// 测试浅克隆
public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建原型对象(执行一次复杂初始化)
        Equipment equip = new Equipment("木剑");
        GameRole prototype = new GameRole("原型角色", 1, equip);

        // 克隆出3个新角色(无需重复初始化)
        GameRole role1 = prototype.clone();
        role1.setName("玩家1");
        GameRole role2 = prototype.clone();
        role2.setName("玩家2");
        GameRole role3 = prototype.clone();
        role3.setName("玩家3");

        // 问题:修改原型的装备,会影响所有克隆对象(因为引用同一份)
        prototype.getEquipment().setWeapon("铁剑");
        System.out.println(role1.getEquipment().getWeapon()); // 输出"铁剑"(被影响)
    }
}

​
2. 深克隆:复制 "全部细节"

深克隆会递归复制所有属性,包括引用类型 ------ 新对象和原对象的引用属性完全独立,修改互不影响。常见实现方式有两种:

  • 手动对引用类型属性递归克隆;
  • 通过序列化(将对象转成字节流再恢复)实现(更通用)。

序列化实现深克隆示例:

java 复制代码
​
import java.io.*;

// 注意:需要实现Serializable接口
class GameRole implements Cloneable, Serializable {
    private String name;
    private int level;
    private Equipment equipment; // 引用类型也需实现Serializable

    // 深克隆:通过序列化
    public GameRole deepClone() throws IOException, ClassNotFoundException {
        // 1. 序列化:将对象写入字节流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        // 2. 反序列化:从字节流恢复对象(新对象)
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (GameRole) ois.readObject();
    }
    // 其他代码省略
}

// 装备类也需实现Serializable
class Equipment implements Serializable {
    private String weapon;
    // 代码省略
}

// 测试深克隆
public class DeepCloneDemo {
    public static void main(String[] args) throws Exception {
        Equipment equip = new Equipment("木剑");
        GameRole prototype = new GameRole("原型角色", 1, equip);

        GameRole role1 = prototype.deepClone();
        role1.setName("玩家1");

        // 修改原型的装备,克隆对象不受影响
        prototype.getEquipment().setWeapon("铁剑");
        System.out.println(role1.getEquipment().getWeapon()); // 输出"木剑"(独立)
    }
}

​
三、适合用原型模式的 3 类场景
  1. 对象创建成本高 :如果对象构造需要大量资源(比如数据库查询、文件 IO),克隆比new更高效(比如报表系统中,批量生成结构相同、数据略有差异的报表);
  2. 需要动态生成对象:比如插件系统,无法提前知道要创建的对象类型,可通过克隆已加载的插件实例扩展功能;
  3. 避免构造函数副作用:如果构造函数中有非初始化逻辑(比如注册监听器),重复调用可能导致意外(克隆可跳过构造函数)。
四、避坑指南
  • 浅克隆可能导致 "意外共享":如果对象包含引用类型,优先用深克隆(除非明确需要共享引用);
  • 序列化深克隆的限制:被克隆的类及所有引用类型必须实现Serializable, transient 修饰的属性不会被克隆;
  • 克隆与构造函数:Object.clone()不会调用构造函数,若初始化逻辑依赖构造函数,需在克隆后手动补充。
总结

原型模式是 "高效复制" 的代名词 ------ 当你需要批量创建相似对象时,与其重复new和初始化,不如 "克隆原型 + 修改细节"。记住:浅克隆适合简单对象,深克隆适合含引用类型的复杂对象,根据场景选择即可。

相关推荐
开开心心就好13 分钟前
本地文件搜索工具,双击Ctrl就能呼出搜索
java·服务器·前端·支持向量机·ffmpeg·音视频·模拟退火算法
独隅17 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.25 分钟前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻26 分钟前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS31 分钟前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel42 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书1 小时前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
John_Hui1 小时前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David1 小时前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
不吃鱼的羊1 小时前
如何应对恐惧
java