创建型设计模式-原型模式,Java怎么实现

一、什么是原型模式

1.1 概述

一句话:用一个已经创建的实力作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需依赖于构造函数。使用原型模式,可以在运行时动态地创建对象,避免了使用显式的构造函数进行对象创建的过程。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

在Java中,原型模式的实现通常需要实现Cloneable接口,并重写clone()方法。Cloneable接口是一个标记接口,用于指示该类可以被克隆。

1.2 原型模式的结构

graph LR A(原型模式) B(抽象原型类) C(具体原型类) D(访问类) E(规定了具体原型对象必须实现clone方法) F(实现抽象原型类的clone方法,它是可被复制的对象) G(使用具体原型类中的clone方法来复制新的对象) A ---> B ---> E A ---> C ---> F A ---> D ---> G style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px

二、Java中怎么实现原型模式

Java中原型模式的克隆分为深克隆和浅克隆。

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

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

graph LR A(Java实现原型模式) B(浅克隆) C(深克隆) D(序列化和反序列化) E(自定义克隆方法) A ---> B A ---> C C ---> D C ---> E style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px

2.1 在Java中如何实现浅克隆

Java中的克隆操作默认是浅拷贝,即复制对象本身,但不复制对象内部的引用类型数据。

下面是浅克隆实现的案例

  • 创建一个可克隆的抽象基类或接口。该基类或接口将定义克隆方法,用于复制对象。
java 复制代码
public abstract class Prototype implements Cloneable {
    public abstract Prototype clone();
}
  • 创建具体类,该类将实现克隆方法并定义对象的特定行为。(具体类中实现克隆方法。使用super.clone()方法来复制对象,并进行类型转换。)
java 复制代码
public class ConcretePrototype extends Prototype {
    @Override
    public Prototype clone() {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 在客户端代码中使用原型对象进行克隆操作。
java 复制代码
public class Test {

    public static void main(String[] args) {
        Prototype original = new ConcretePrototype();
        Prototype clone = original.clone();
    }
    
}

2.2 在Java中如何实现深克隆

在Java中,有两种方法可以实现深克隆。

graph LR A(java实现深克隆) ---> B(序列化和反序列化) A(java实现深克隆) ---> C(自定义克隆方法) style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px

2.2.1 通过实现Serializable接口进行序列化和反序列化

  • 在需要深克隆的类中实现Serializable接口。

  • 将对象写入输出流并进行序列化。

  • 从输入流中读取对象并进行反序列化。

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

public class Demo implements Serializable {
    private String name;
    private MyObject myObject;

    // 构造函数和其他方法

    public Demo deepCopy() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (Demo) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

2.2.2 使用自定义的深克隆方法

  • 在需要深克隆的类中实现自定义的深克隆方法。

  • 在深克隆方法中创建一个新对象,并将原对象的属性值逐一复制到新对象中。对于引用类型的属性,可以使用其自身的深克隆方法或递归进行克隆。

java 复制代码
public class Demo {
    private String name;
    private MyObject myObject;

    // 构造函数和其他方法

    public Demo deepCopy() {
        Demo newObject = new Demo();
        newObject.setName(this.name);
        newObject.setMyObject(this.myObject.deepCopy()); // 假设MyObject类也实现了深拷贝方法
        return newObject;
    }
}

2.3 spring中那些非常好用的原型模式工具类

Spring框架还提供了其他一些原型模式相关的工具类和功能,包括以下几个:

graph LR A(Spring提供的原型模式工具类) B(ObjectFactory) C(PrototypeScope) D(ObjectProvider) E(Lookup注解) A ---> B A ---> C A ---> D A ---> E style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px

ObjectFactory: ObjectFactory是Spring提供的一个用于创建原型对象的接口,它可以用于在运行时动态地创建对象,而无需显式地调用构造函数。ObjectFactory接口有一个重要的方法getObject(),用于获取原型对象的实例。该方法在每次调用时都会返回一个新的对象实例,因此适用于创建原型对象。

PrototypeScope: PrototypeScope是Spring框架中的一个作用域实现,用于定义原型作用域的Bean。可以通过在Bean定义中指定@Scope("prototype")注解或在XML配置中使用元素将Bean定义为原型作用域。使用PrototypeScope作用域,每次获取Bean时都会创建一个新的实例。

ObjectProvider: ObjectProvider是Spring 4.3引入的一个接口,用于获取对象实例。它可以用于获取原型对象的实例,类似于ObjectFactory。ObjectProvider提供了更多的方法来获取和管理对象实例,例如getIfAvailable()、getIfUnique()等。

@Lookup注解:@Lookup注解是Spring框架中的一个元注解,用于标记一个方法,表示该方法应该返回一个原型对象。使用@Lookup注解,可以在运行时动态地获取原型对象的实例。

示例:展示了如何在Spring中使用ObjectFactory创建原型对象:

java 复制代码
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class MyPrototype {
    private static int count = 0;
    private int id;

    public MyPrototype() {
        id = count++;
    }

    public int getId() {
        return id;
    }
}

public class PrototypeExample {
    public static void main(String[] args) {
        ObjectFactory<MyPrototype> objectFactory = new ObjectFactory<MyPrototype>() {
            @Override
            public MyPrototype getObject() {
                return new MyPrototype();
            }
        };

        // 使用ObjectFactory创建原型对象
        MyPrototype obj1 = objectFactory.getObject();
        MyPrototype obj2 = objectFactory.getObject();

        System.out.println("Object 1 ID: " + obj1.getId());
        System.out.println("Object 2 ID: " + obj2.getId());
    }
}

在上面的示例中,使用ObjectFactory创建了一个MyPrototype的原型对象。每次调用getObject()方法时,都会返回一个新的MyPrototype对象实例。通过多次调用,可以看到每个对象实例都有不同的ID。

ObjectFactory接口可以在Spring框架中的各个地方使用,例如在Bean定义中声明原型作用域的Bean时,可以使用ObjectFactory来创建原型对象。

2.4 spring的BeanUtils.copyProperties 有用到原型设计模式吗

BeanUtils.copyProperties()方法并**没有**直接使用原型设计模式。它是Spring框架中的一个工具方法,用于将一个Java对象的属性值复制到另一个Java对象中。

在BeanUtils.copyProperties()方法的实现中,并没有显式地使用原型模式的概念。它主要通过反射机制来获取和设置对象的属性值,然后将源对象的属性值复制到目标对象中。

然而,BeanUtils.copyProperties()方法在某种程度上**可以看作是一种原型模式的变种**。它通过复制属性值的方式创建一个新的目标对象,而不需要显式地调用构造函数。这种方式类似于原型模式中通过复制现有对象来创建新对象的方式,但并不是严格的原型模式实现。

总的来说,尽管BeanUtils.copyProperties()方法在某种程度上具有类似原型模式的特征,但它并不是原型模式的典型实现。它更多地用于对象属性值的复制和转换,而不是动态创建对象实例。

三、原型模式的意义和Java中的使用场景

3.1 意义

在运行期建立和删除原型。

3.2 Java中的使用场景

graph LR A(使用场景) B(多线程环境) C(缓存管理) D(对象池管理) E(动态代理) A ---> B A ---> C A ---> D A ---> E style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
  • 多线程环境:在多线程环境中,原型模式工具类可以用于创建线程安全的对象实例。每个线程可以通过原型模式工具类获取自己的对象实例,避免了多个线程之间的竞争和共享状态的问题。

  • 缓存管理:原型模式工具类可以用于缓存管理,特别是在需要将某个对象作为模板复制多个对象的情况下。通过原型模式工具类,可以复制模板对象,并对每个复制的对象进行个性化的修改,从而实现自定义的缓存对象。

  • 对象池管理:在需要管理对象池的场景中,原型模式工具类可以用于创建和管理对象池中的对象。通过原型模式工具类,可以复制原型对象来填充对象池,并在需要时获取对象实例,避免了频繁创建和销毁对象的开销。

  • 动态代理:在动态代理中,原型模式工具类可以用于创建代理对象。每次需要代理对象时,可以通过原型模式工具类获取一个新的代理对象实例,以确保每个代理对象都具有独立的状态和行为。

四、原型模式的优缺点和注意事项

  • 优点

    • 性能提高。

    • 逃避构造函数的约束。

  • 缺点

    • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

    • 必须实现 Cloneable 接口。

  • 注意事项

    • 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
相关推荐
hrrrrb1 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶1 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯6 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友6 小时前
什么是断言?
前端·后端·安全
程序员小凯8 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
i学长的猫8 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636028 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
hello 早上好8 小时前
深入 Spring 依赖注入底层原理
数据库·sql·spring
茯苓gao9 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django