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

2023年 10月 12日 43.9k 0

一、什么是原型模式

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中的克隆操作默认是浅拷贝,即复制对象本身,但不复制对象内部的引用类型数据。

下面是浅克隆实现的案例

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

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

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

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 使用自定义的深克隆方法

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

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

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创建原型对象:

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 objectFactory = new ObjectFactory() {
            @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 读取二进制流。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论