设计模式之原型模式


参考文章1: https://cloud.tencent.com/developer/article/2325501
参考文章2: https://blog.csdn.net/dl962454/article/details/114780240
参数文章3: https://blog.csdn.net/qq_37113604/article/details/81168224

一、谈谈什么是对象克隆?

在学习原型模式之前,首先要理解对象克隆的概念。
在Java中,对象克隆是指创建一个现有对象的副本,对象克隆通常用于在不影响原始对象的情况下创建一个相同状态的新对象。

  • Java中的对象克隆可以通过实现 Cloneable 接口和重写 clone() 方法来实现。
  • 在使用克隆时,可以使用clone()方法创建对象的副本,该方法返回一个新的对象,该对象具有与原始对象相同的属性值。
  • Java 中的 clone() 方法执行的是浅拷贝,这意味着克隆的对象和原始对象共享相同的引用类型字段,如果需要实现深拷贝,即克隆对象及其所有引用类型字段的副本,就需要在 clone() 方法中进行相应的处理。
    在这里插入图片描述

二、谈谈什么是深拷贝和浅拷贝,有什么区别?

在Java中,对象拷贝可以分为浅拷贝和深拷贝两种方式,它们之间的区别在于拷贝对象时是否创建了原始对象的副本,以及对引用类型字段的处理方式。

2.1 深拷贝

深拷贝是指创建一个新对象,该对象的字段值与原始对象完全相同,包括引用类型字段。在深拷贝中,不仅复制了对象的基本类型字段,还创建了新的对象来存储引用类型字段的副本。这意味着修改拷贝对象的引用类型字段不会影响原始对象的引用类型字段,因为它们引用的是不同的对象。

2.2 浅拷贝

浅拷贝是指创建一个新对象,该对象的字段值与原始对象完全相同,但对于引用类型字段,它们共享相同的引用。换句话说,浅拷贝只复制了对象中的基本类型字段,而对于引用类型字段,只是复制了引用,没有创建新的对象。
在浅拷贝中,修改拷贝对象的引用类型字段会影响到原始对象的引用类型字段。这是因为原始对象和拷贝对象共享相同的引用,所以它们指向相同的内存地址。

2.3 深拷贝实现方式

2.3.1 Cloneable接口

Object默认的clone方法实际是对域的简单拷贝,对于简单数据类型,是值的拷贝;
对于复杂类型的字段,则是指针地址的拷贝,clone后的对象和原对象指向的还是一个地址空间。
所以说默认的clone方法是浅克隆;

package com.dl.JavaBase;
class Car implements Cloneable{
    private String brand;//品牌
    private int maxSpeed;//最高时速

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

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", maxSpeed=" + maxSpeed +
                '}';
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
}
public class Person implements Cloneable {
    private String name;
    private Car car;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car=" + car +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Person(String name, Car car) {
        this.name = name;
        this.car = car;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Car car = new Car("audi", 150);
        Person person=new Person("ding",car);
        Person person1= (Person) person.clone();
        System.out.println("修改car之前:");
        System.out.println(person);
        System.out.println(person1);
        System.out.println("修改car之后:");
        car.setBrand("benchi");
        car.setMaxSpeed(200);
        System.out.println(person);
        System.out.println(person1);
        System.out.print("使用Object默认的clone方法:");
        System.out.println(person.getCar()==person1.getCar());
    }
}

执行结果:
在这里插入图片描述
这种克隆方式显然表示原始对象和克隆对象的Car是同一个 引用。也就是说,Car对象没有被克隆。如果修改了Car对象的值,原始对象和克隆对象都将会发生变化。这并不是我们希望看到的。

所以,我们需要连对象里面的对象也要是一个新的对象。每一个属性都被完全拷贝,这才是深克隆。
为了实现深度克隆,我们需要对Person中的clone方法进行改造一下,getCar()测试代码不变。

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person= (Person) super.clone();
        person.setCar((Car) person.getCar().clone());
        return person;
    }

再次进行测试:
在这里插入图片描述
这么做就要在super.clone的基础上 继续对非基本类型的对象递归地再次clone。显然这么方式是繁琐的且不可靠的。

2.3.2 Java自身序列化

使用java自身的序列化转为二进制数 ,再反序列化为对象。
ObjectStream序列化的工具类

package com.dl.JavaBase;

import java.io.*;

public class SerialiazableUtil {

    public SerialiazableUtil() {
        throw new AssertionError();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepCloneObject(Object object) throws IOException {
        T deepClone = null;
        ObjectInputStream ois = null;
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
        )
        {
            oos.writeObject(object);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos
                    .toByteArray());
            ois = new ObjectInputStream(bais);
            deepClone = (T)ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                ois.close();
            }
        }
        return deepClone;
    }
}

测试类:

package com.dl.JavaBase;

import java.io.IOException;
import java.io.Serializable;

class Car implements Serializable {
    private static final long serialVersionUID = 4982206063131788088L;
    private String brand;//品牌
    private int maxSpeed;//最高时速

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", maxSpeed=" + maxSpeed +
                '}';
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
}
public class Person implements Serializable {

    private static final long serialVersionUID = 6957528274628957691L;
    private String name;
    private Car car;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car=" + car +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Person(String name, Car car) {
        this.name = name;
        this.car = car;
    }

    public static void main(String[] args) throws CloneNotSupportedException, IOException {
        Person person=new Person("ding",new Car("audi",150));
        Person person1= SerialiazableUtil.deepCloneObject(person);
        System.out.print("Java默认序列化方式:");
        System.out.println(person.getCar()==person1.getCar());
    }
}

运行结果:
在这里插入图片描述
其他方式还可以是用序列化工具如fastjson进行序列化和反序列化进行对象clone。

2.3.3 FastJson序列化

Maven依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

Car类

package com.dl.JavaBase;

import java.io.Serializable;

public class Car implements Serializable {
    private static final long serialVersionUID = 4982206063131788088L;
    private String brand;//品牌
    private int maxSpeed;//最高时速

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", maxSpeed=" + maxSpeed +
                '}';
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
}

Person类

package com.dl.JavaBase;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;
public class Person implements Serializable {

    private static final long serialVersionUID = 6957528274628957691L;
    private String name;
    private Car car;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car=" + car +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Person(String name, Car car) {
        this.name = name;
        this.car = car;
    }

    public static void main(String[] args) throws CloneNotSupportedException, IOException {
        Person person=new Person("ding",new Car("audi",150));
        //Person person1 = JSONObject.parseObject(JSONObject.toJSONString(person), Person.class);
        Person person1 = JSONObject.parseObject(JSONObject.toJSONBytes(person), Person.class);
        System.out.println("fastjson方式:");
        System.out.println(person.getCar()==person1.getCar());
    }
}

运行结果:
在这里插入图片描述

2.3.4 深拷贝总结

实现对象克隆主要有两种方式:
1、实现Cloneable接口并重写其中的clone()方法完成对象的浅拷贝

  • Object默认的clone方法实际是对域的简单拷贝,对于简单数据类型,是值的拷贝;
  • 对于复杂类型的字段,则是指针地址的拷贝,clone后的对象和原对象指向的还是一个地址空间。
  • 所以说默认的clone方法是浅克隆。
  • 想要实现深克隆需要复杂类实现中为每个类都实现Cloneable接口并重写clone方法(复杂类中的对象也要是新的对象)这么做就要在super.clone的基础上 继续对非基本类型的对象递归的再次clone.
    显然这么方式是繁琐的且不可靠的。
    2、实现序列化接口Serializable,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
  • 基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是通过编译器完成的,
  • 不是在运行时抛出异常,这汇总方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来中是好过把问题留到运行时。

三、如何解决java对象拷贝的性能问题

在Java中,对象拷贝可能面临性能问题,特别是在处理大型对象或复杂对象图时,以下是一些可以帮助解决 Java 对象拷贝性能问题的方法。
使用原型模式:通过复制或克隆现有对象来创建新对象,而无需依赖于显式的实例化过程,从而避免了直接创建新对象的开销,原型模式可以通过实现 Cloneable 接口和重写 clone() 方法来实现。

  • 1、使用浅拷贝:如果你只需要复制对象的基本类型字段,并且可以共享引用类型字段,那么浅拷贝是一个更高效的选择,浅拷贝只涉及字段的复制,因此比深拷贝更快。

  • 2、使用构造函数:手动编写一个构造函数,以根据原始对象的属性创建新对象,这种方式可以避免调用 clone() 方法或实现 Cloneable 接口的开销。

  • 3、使用序列化和反序列化:使用 Java 对象的序列化和反序列化机制可以实现深拷贝,通过将对象序列化为字节流,然后反序列化成新的对象,可以创建对象及其所有引用字段的完全独立副本,但序列化和反序列化也会带来一定的性能开销。

  • 4、使用第三方库:有些第三方库提供了更高效的对象拷贝实现,例如 Apache Commons 库提供了 SerializationUtils.clone() 方法,用于快速实现对象的深拷贝。

  • 5、使用对象池:如果你需要频繁地拷贝对象,可以考虑使用对象池,对象池在初始阶段创建一组对象,并在需要时从池中获取和返回对象,以避免频繁地创建和销毁对象。

  • 6、考虑重构:有时性能问题可能源于对象本身的设计,在某些情况下,可以通过优化对象的结构或减少不必要的字段来改善性能。

四、学习什么是原型模式

原型模式是一种创建型设计模式,作用域组件的创建,其主要目的是通过复制或克隆现有对象来创建新对象,而无需依赖于显式的实例化过程

显式的实例化过程:Java对象的实例化过程包括类加载、验证、准备、解析、初始化、内存分配、零值初始化和对象头设置,以及最终创建引用的过程

原型模式通过复制现有对象的状态来创建新对象,从而避免了直接创建新对象的开销,原型模式可以通过实现 Cloneable 接口和重写 clone() 方法来实现。

在原型模式中,原型对象作为被复制的对象,可以称为原型。克隆方法是原型模式的核心部分,它定义了如何复制原型对象。通过克隆方法,我们可以创建一个与原型对象具有相同状态的新对象。

原型模式的主要优点是可以在运行时动态创建对象,,避免了显式的实例化过程,提高了创建对象的效率。它还提供了一种简单的方式来创建具有相同状态的对象,通过修改克隆得到的对象,可以满足不同的需求。另外,原型模式也能够隐藏对象的创建细节,使得客户端代码与具体类解耦。

但在使用原型模式需要注意一些问题。首先克隆对象可能包含对其他对象的引用,这可能导致对象图的复制,需要特别小心处理。其次,克隆过程可能会比直接创建对象更复杂,需要对克隆方法进行合理的实现。

原型模式提供了一种创建对象的简单而高效的方式,可以在运行时动态地创建具有相同状态的新对象,它在需要创建相似对象或隐藏对象创建细节时非常有用。
在这里插入图片描述

五、原型模式上手实战

// 原型接口
interface Prototype extends Cloneable {
    Prototype clone();
}

// 具体原型类
class ConcretePrototype implements Prototype {
    private String name;

    public ConcretePrototype(String name) {
        this.name = name;
    }

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

    public String getName() {
        return name;
    }

    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        ConcretePrototype prototype = new ConcretePrototype("Prototype 1");
        System.out.println("Original object: " + prototype.getName());

        ConcretePrototype clonedObject = (ConcretePrototype) prototype.clone();
        System.out.println("Cloned object: " + clonedObject.getName());

        clonedObject.setName("Prototype 2");
        System.out.println("Modified cloned object: " + clonedObject.getName());
    }
}

在上面的示例中,我们定义了一个原型接口 Prototype,其中包含了一个 clone() 方法用于复制自身对象。

然后,我们创建了一个具体的原型类 ConcretePrototype,实现了 Prototype 接口,并重写了 clone() 方法。

在客户端代码中,我们首先创建了一个原型对象 prototype,然后通过调用 clone() 方法来复制原型对象,得到一个克隆对象 clonedObject

通过修改克隆对象的属性,我们可以验证克隆对象和原型对象是相互独立的,互不影响。

六、原型模式的应用场景

原型模式通常在以下类开发场景下使用;

  • 1、需要创建一个对象的成本较大,例如涉及到数据库操作、网络请求等耗时操作;
  • 2、需要创建的对象与已有对象具有相似的属性,只有部分属性需要修改。
  • 3、需要隐藏对象的创建细节,使客户端代码与具体类解耦。

当然,还有一些应用场景,需要用到原型模式。

  • 复杂对象的创建:当创建一个复杂对象的过程很繁琐或耗时时,可以使用原型模式来复制一个已有对象,避免重复创建。
  • 原型注册表:使用原型模式可以创建一个对象的集合,并在需要时从集合中复制已有对象,提高对象的创建效率。
  • 工厂方法模式的替代:原型模式可以作为工厂方法模式的替代,当需要创建的对象具有相同的基类或接口,并且只有部分属性需要修改时,原型模式比工厂方法模式更加灵活。

总之,原型模式适用于创建成本高、创建过程复杂或需要隐藏创建细节的对象,通过克隆现有对象来创建新对象,可以提高创建对象的效率,同时也能够灵活地满足不同的需求。
在这里插入图片描述

七、原型模式面试题

7.1 什么是原型模式?

原型模式是一种创建型设计模式,通过复制或克隆现有对象来创建新对象,而无需依赖于显式的实例化过程。

7.2 如何实现原型模式?

在Java中,可以通过实现Cloneable接口和重写clone()方法来实现原型模式。clone()方法可以复制现有对象的状态,并创建一个与原型对象具有相同状态的新对象。

7.3 克隆方法与构造方法有什么区别?

克隆方法是在现有对象的基础上创建一个新对象,而构造方法是通过实例化类来创建新对象。克隆方法可以复制现有对象的状态,而构造方法需要手动设置新对象的状态。

7.4 原型模式的优点是什么?

原型模式可以在运行时动态创建对象,避免了显式的实例化过程,提高了创建对象的效率。它还提供了一种简单的方式来创建具有相同状态的对象,并能够隐藏对象的创建细节,使得客户端代码与具体类解耦。

7.5 原型模式的适用场景有哪些?

原型模式适用于创建成本高、创建过程复杂或需要隐藏创建细节的对象。一些常见的应用场景包括复杂对象的创建、原型注册表和作为工厂方法模式的替代。

7.6 原型模式的局限性是什么?

使用原型模式需要注意克隆对象可能包含对其他对象的引用,这可能导致对象图的复制,需要特别小心处理。克隆过程可能会比直接创建对象更复杂,需要对克隆方法进行合理的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值