Java设计模式之《原型模式》--深、浅copy

目录

1、原型模式

1.1、定义

1.2、使用场景

1.3、与bean的区别

[2、浅拷贝 vs 深拷贝](#2、浅拷贝 vs 深拷贝)

2.1、分类

1、浅拷贝

2、深拷贝

2.2、实现方式

[1、手动 clone 引用成员](#1、手动 clone 引用成员)

2、序列化做深拷贝

3、原型注册表


前言

原型模式属于创建型模式。用于创建重复的对象,同时又能保证性能。要求原型对象实现一个"克隆"自身的方法,通过调用这个方法来创建一个新的实例对象


1、原型模式

1.1、定义

通过复制(克隆)已有对象来创建新对象,而不是通过 new 构造函数来创建。原型对象保存了实例的初始状态,客户端通过克隆原型得到新的对象。

如下图所示:

1.2、使用场景

什么时候用原型模式更合适?

1、对象创建成本高(比如大量属性赋值、复杂初始化);

2、需要运行时决定要创建哪种具体类实例;

3、需要大量相似对象(使用原型复制比重复 new 和初始化更高效)。

1.3、与bean的区别

1、原型模式(Prototype Pattern):

一种"创建型设计模式",通过复制(克隆)已有对象来创建新对象,关注如何复制对象(clone / 拷贝构造 / 序列化等)。

通常靠对象自身实现复制逻辑(Object.clone、copy constructor、序列化等)。

2、Spring 的 prototype scope:

Spring 容器中一个"作用域/生命周期策略",表示每次从容器获取该 bean 都会创建一个新的实例,关注的是容器如何管理 bean 的生命周期。

由容器负责新建(调用构造器/工厂方法并做依赖注入),并不要求对象实现 clone。

具体可参考:Spring的Bean原型模式下的使用_spring 原型bean-CSDN博客文章浏览阅读1.2k次,点赞33次,收藏30次。摘要:本文探讨了Spring中原型(Prototype)模式Bean的使用问题与解决方案。问题原因包括@Autowired注入只初始化一次、代理模式问题及不当获取方式。提供了四种解决方案:通过ApplicationContext获取、使用ObjectProvider、Lookup方法和Provider接口。分析了原型模式的适用场景:有状态Bean、线程不安全对象、需要新实例及避免副作用的场景。最后指出使用注意事项,包括内存管理、性能影响、依赖管理和测试复杂性,强调需权衡资源开销与功能需求。_spring 原型beanhttps://dyclt.blog.csdn.net/article/details/149182196?spm=1011.2415.3001.5331


2、浅拷贝 vs 深拷贝

2.1、分类

1、浅拷贝

复制对象本身,但对象内部的引用类型字段(比如其他对象、集合)只是复制引用,仍然指向同一个子对象。

换句话说,"复制的是外壳,里面的东西是共享的"。

⚠️注意:

如果原型对象的成员变量是值类型(byte,short,int,long,char,double,float,boolean).那么就直接复制;如果是复杂的类型,(如枚举、对象)就只复制对应的内存地址

2、深拷贝

不仅复制对象本身,也递归复制它引用的所有可变子对象,得到真正独立的完整副本

换句话说,"里面的东西也全部复制了一份"。

2.2、实现方式

实现方式概述

  • 使用 Cloneable + Object.clone()(容易实现浅拷贝,做深拷贝需手动 clone 引用字段)
  • 使用拷贝构造函数(推荐:明确、可控)
  • 使用序列化(Serializable)深拷贝(简便但开销高)
  • 使用原型注册表(Registry)统一管理可克隆原型

1:浅拷贝(使用 Cloneable、super.clone())

java 复制代码
// Address.java
class Address implements Cloneable {
    String city;
    Address(String city) { this.city = city; }
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    @Override
    public String toString() { return "Address{city='" + city + "'}"; }
}

// PersonShallow.java
class PersonShallow implements Cloneable {
    String name;
    int age;
    Address address; // 引用类型

    PersonShallow(String name, int age, Address address) {
        this.name = name; this.age = age; this.address = address;
    }

    @Override
    public PersonShallow clone() {
        try {
            // super.clone() 做的是浅拷贝:基本类型复制,引用复制引用
            return (PersonShallow) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    @Override
    public String toString() {
        return "PersonShallow{name='" + name + "', age=" + age + ", address=" + address + "}";
    }
}

// MainShallowDemo.java
public class MainShallowDemo {
    public static void main(String[] args) {
        Address addr = new Address("Beijing");
        PersonShallow p1 = new PersonShallow("Alice", 30, addr);
        PersonShallow p2 = p1.clone();

        System.out.println("Before change:");
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);

        // 修改 p2 的 address,会影响 p1(因为浅拷贝共享同一个 Address)
        p2.address.city = "Shanghai";

        System.out.println("After change:");
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
    }
}

修改克隆对象中引用类型会影响原对象。

2:深拷贝(clone 内部手动 clone 引用成员或使用序列化)

1、手动 clone 引用成员

(建议)

java 复制代码
// PersonDeep.java
class PersonDeep implements Cloneable {
    String name;
    int age;
    Address address;

    PersonDeep(String name, int age, Address address) {
        this.name = name; this.age = age; this.address = address;
    }

    @Override
    public PersonDeep clone() {
        try {
            PersonDeep copy = (PersonDeep) super.clone();
            // 手动深拷贝引用对象
            copy.address = this.address.clone();
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    @Override
    public String toString() {
        return "PersonDeep{name='" + name + "', age=" + age + ", address=" + address + "}";
    }
}

// MainDeepDemo.java
public class MainDeepDemo {
    public static void main(String[] args) {
        Address addr = new Address("Beijing");
        PersonDeep p1 = new PersonDeep("Bob", 40, addr);
        PersonDeep p2 = p1.clone();

        System.out.println("Before change:");
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);

        p2.address.city = "Guangzhou"; // 不会影响 p1
        System.out.println("After change:");
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
    }
}

2、序列化做深拷贝

(可用于任意深度,但性能/可读性较差)

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

// 使用可序列化的类
class AddressS implements Serializable {
    String city;
    AddressS(String city) { this.city = city; }
    public String toString() { return "AddressS{city='" + city + "'}"; }
}

class PersonS implements Serializable {
    String name;
    int age;
    AddressS address;
    PersonS(String name, int age, AddressS address) {
        this.name = name; this.age = age; this.address = address;
    }
    public String toString() { return "PersonS{name='" + name + "', age=" + age + ", address=" + address + "}"; }
}

public class SerializeDeepCopy {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepCopy(T obj) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(obj);
            out.flush();
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bis);
            return (T) in.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        PersonS p1 = new PersonS("Cathy", 28, new AddressS("Shenzhen"));
        PersonS p2 = deepCopy(p1);
        p2.address.city = "Hangzhou";
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
    }
}

3、原型注册表

java 复制代码
import java.util.*;

// 简单的 Prototype 接口(返回 Object 或 Prototype 的 clone)
interface Prototype {
    Prototype clone();
}

// 一个具体原型
class ConcretePrototype implements Prototype {
    private String id;
    private Map<String, String> data = new HashMap<>();
    ConcretePrototype(String id) { this.id = id; }
    void put(String k, String v) { data.put(k, v); }
    String get(String k) { return data.get(k); }

    @Override
    public Prototype clone() {
        // 简单示例做浅拷贝 + 拷贝集合内容以避免共享
        ConcretePrototype copy = new ConcretePrototype(this.id);
        copy.data = new HashMap<>(this.data);
        return copy;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{id='" + id + "', data=" + data + "}";
    }
}

class PrototypeRegistry {
    private Map<String, Prototype> registry = new HashMap<>();
    void register(String key, Prototype prototype) { registry.put(key, prototype); }
    Prototype create(String key) {
        Prototype p = registry.get(key);
        return (p != null) ? p.clone() : null;
    }
}

// Usage
public class PrototypeRegistryDemo {
    public static void main(String[] args) {
        PrototypeRegistry reg = new PrototypeRegistry();
        ConcretePrototype protoA = new ConcretePrototype("A");
        protoA.put("name", "TemplateA");
        reg.register("A", protoA);

        ConcretePrototype instance = (ConcretePrototype) reg.create("A");
        instance.put("name", "InstanceFromA");

        System.out.println("protoA = " + protoA);
        System.out.println("instance = " + instance);
    }
}

总结

原型模式通过对象的复制机制,提供了一种高效创建相似对象的方式。浅克隆适用于简单对象深克隆则适合复杂对象结构

在 Java 中,借助 Cloneable 接口和 Clone() 方法,可以轻松实现原型模式。


参考文章:

1、Java 设计模式:原型模式详解_java 原型模式-CSDN博客文章浏览阅读533次,点赞5次,收藏10次。原型模式的核心思想是:通过克隆已有对象(原型)来生成新对象,而不是通过构造函数重新创建。它利用对象的复制机制,提高创建效率,并支持动态扩展。_java 原型模式https://blog.csdn.net/NepalTrip/article/details/147085502?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522ae2f8b02105801ca1c69be6444a5bb9c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=ae2f8b02105801ca1c69be6444a5bb9c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~sobaiduend~default-2-147085502-null-null.nonecase&utm_term=java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

2、设计模式精讲-CSDN博客文章浏览阅读10w+次,点赞607次,收藏4.9k次。设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。总体来说,设计模式分为三大类:5种创建型模式、7种结构型模式、11种行为型模式_java 设计模式https://blog.csdn.net/a745233700/article/details/120371090?ops_request_misc=%257B%2522request%255Fid%2522%253A%252240413392cc5f347aebaa2a1bfe2e970e%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=40413392cc5f347aebaa2a1bfe2e970e&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-6-120371090-null-null.nonecase&utm_term=java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450

相关推荐
张较瘦_10 小时前
[论文阅读] 软件工程 | 告别“线程安全玄学”:基于JMM的Java类静态分析,CodeQL3分钟扫遍GitHub千仓错误
java·论文阅读·安全
A尘埃12 小时前
智慧零售全渠道业务中台系统
java·零售
尘鹄12 小时前
go 初始化组件最佳实践
后端·设计模式·golang
小wanga15 小时前
C++知识
java·开发语言·c++
我是渣哥15 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
工一木子15 小时前
深入Java并发:锁机制原理剖析与性能优化实战
java·性能优化·并发·
你我约定有三15 小时前
java--写在 try 中的创建连接
java·开发语言
ERP老兵-冷溪虎山15 小时前
Python/JS/Go/Java同步学习(第三篇)四语言“切片“对照表: 财务“小南“纸切片术切凭证到崩溃(附源码/截图/参数表/避坑指南/老板沉默术)
java·javascript·python·golang·中医编程·四语言同步学习·职场生存指南
科技树支点15 小时前
无GC的Java创新设计思路:作用域引用式自动内存管理
java·python·go·web·编程语言·编译器