原型模式(创建型)

一、前言

原型模式是一种创建型设计模式,它允许在运行时通过克隆现有对象来创建新对象,而不是通过常规的构造函数创建。在原型模式中,一个原型对象可以克隆自身来创建新的对象,这个过程可以通过深度克隆或浅克隆来实现。简单说原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节。

原型模式包含三个角色:

①Prototype(抽象原型类):定义用于克隆自身的接口,通常是一个抽象类或接口,其中声明了一个克隆方法 clone(),用于创建一个新的对象,具体的克隆操作由子类实现。

②ConcretePrototype(具体原型类):实现 Prototype 接口,实现 clone() 方法,完成对象的克隆操作。每个具体原型类都有自己的一些属性和方法,不同的具体原型类之间具有不同的实现方式。

③Client(客户类):使用原型模式创建新的对象,在原型模式中,客户端通过克隆接口来创建新的对象,而不是通过实例化的方式。客户端需要获取一个原型对象,并通过克隆方法来创建新的对象,从而避免了创建新对象的开销。

原型模式的克隆分为浅拷贝和深拷贝两种。

二、浅拷贝

创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

这里抽象原型类不用自己创建,都是Object,里面有clone()方法:

具体原型类实现Cloneable,里面重写clone方法,直接super调用父类的clone方法即可:

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

/**
 * @Author dengyifan
 * @create 2023/11/10 10:19
 * @description
 */
public class UserPrototype implements Cloneable{

    private String name;

    private Integer age;

    private List<String> messages;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<String> getMessages() {
        return messages;
    }

    public void setMessages(List<String> messages) {
        this.messages = messages;
    }

    @Override
    protected UserPrototype clone() throws CloneNotSupportedException {
        return (UserPrototype) super.clone();
    }
}

客户类:

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * @Author dengyifan
 * @create 2023/11/10 10:22
 * @description
 */
public class PrototypeClient {
    public static void main(String[] args) throws CloneNotSupportedException {
        UserPrototype userPrototype1 = new UserPrototype();

        userPrototype1.setName("tom");
        userPrototype1.setAge(18);
        List<String> messages1 = new ArrayList<>();
        messages1.add("test");
        userPrototype1.setMessages(messages1);

        UserPrototype userPrototype2 = userPrototype1.clone();

        System.err.println(String.format("两个对象是否一致:%s", userPrototype1 == userPrototype2));
        System.err.println(String.format("两个对象的name属性是否一致:%s", userPrototype1.getName() == userPrototype2.getName()));

        System.err.println(String.format("两个对象的messages属性是否一致:%s", userPrototype1.getMessages() == userPrototype2.getMessages()));

        System.err.println(userPrototype1.getMessages());
        System.err.println(userPrototype2.getMessages());

        messages1.clear();
        messages1.add("test1");
        System.err.println(userPrototype1.getMessages());
        System.err.println(userPrototype2.getMessages());


    }
}

运行结果:

通过结果可以看到,通过克隆的对象与原型对象的引用不一致,说明再内存中存在两个不同的对象,一个是原型对象,一个是克隆生成的对象。而这里属性都显示是一致的,结果都是true,说明两个对象的成员对象是同一个,也就是对象本身是复制了,但是其成员的对象再内存中没有复制。并且我们修改原型的messages属性,克隆对象的messages也跟着改变,说明属性确实是同一个对象的引用。

三、深拷贝

深拷贝中除了原型对象与克隆生成的对象被复制以外,里面的属性也需要被复制,即地址都不一样。这里深拷贝实现了Serializable,即进行了序列化。

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

/**
 * @Author dengyifan
 * @create 2023/11/10 15:43
 * @description
 */
public class UserPrototypeDeep implements Serializable {
    private String name;

    private Integer age;

    private List<String> messages;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<String> getMessages() {
        return messages;
    }

    public void setMessages(List<String> messages) {
        this.messages = messages;
    }

    protected UserPrototypeDeep deepClone() throws Exception{
        ByteArrayOutputStream bao=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bao);
        oos.writeObject(this);
        //将对象从流中取出
        ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bis);
        return (UserPrototypeDeep) ois.readObject();
    }
}
java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * @Author dengyifan
 * @create 2023/11/10 15:46
 * @description
 */
public class PrototypeDeepClient {
    public static void main(String[] args) throws Exception {
        UserPrototypeDeep userPrototypeDeep1 = new UserPrototypeDeep();
        userPrototypeDeep1.setAge(19);
        userPrototypeDeep1.setName("tom");
        List<String> messages1 = new ArrayList<>();
        messages1.add("test");
        userPrototypeDeep1.setMessages(messages1);

        UserPrototypeDeep userPrototypeDeep2 = userPrototypeDeep1.deepClone();

        System.err.println(String.format("两个对象是否一致:%s", userPrototypeDeep1 == userPrototypeDeep2));
        System.err.println(String.format("两个对象的name属性是否一致:%s", userPrototypeDeep1.getName() == userPrototypeDeep2.getName()));

        System.err.println(String.format("两个对象的messages属性是否一致:%s", userPrototypeDeep1.getMessages() == userPrototypeDeep2.getMessages()));

        System.err.println(userPrototypeDeep1.getMessages());
        System.err.println(userPrototypeDeep2.getMessages());
        messages1.clear();
        messages1.add("test1");
        System.err.println(userPrototypeDeep1.getMessages());
        System.err.println(userPrototypeDeep2.getMessages());

    }
}

运行结果:

可以看出除了对象本身不一致以外,里面的属性也不一致,当修改原型对象里面引用对象的属性时,克隆对象的属性不会发生变化,即也证明属性不是同一个对象。

对于原型模式的应用场景,主要有以下几点:

  1. 当对象的创建过程非常复杂或耗费大量资源时,可以使用原型模式来复制一个现有对象,从而避免重复创建对象。

  2. 当需要创建多个相似的对象时,可以使用原型模式来减少重复代码,提高代码的重用性。

  3. 当创建对象的过程需要大量的数据准备或配置时,可以使用原型模式来复制一个已经配置好的对象,从而避免重复的数据准备或配置过程。

  4. 当需要动态生成对象的子类时,可以使用原型模式来复制一个已有的对象,并对其进行修改,从而避免重新编写子类的代码。

  5. 当需要保护对象的状态时,可以使用原型模式来创建对象的副本,并将副本交给其他对象使用,从而避免原始对象的状态被修改。

相关推荐
希忘auto5 分钟前
详解MySQL安装
java·mysql
冰淇淋烤布蕾16 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺23 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE39 分钟前
正确使用primefaces的process和update
java·primefaces·jakarta ee
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
布川ku子2 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试