eajur

1、描述

用原型实例指定创建对象的种类,并且通过拷贝这些原型对象的属性来创建新的对象。通俗点的意思就是一个对象无需知道任何创建细节就可以创建出另外一个可定制的对象。可以简单看作为复制、粘贴操作。

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

  • 浅克隆

克隆对象的属性和原对象完全相同,基本类型的属性属于值传递,改变一个对象的值,另一个不会受影响。对于引用数据类型的属性,仍指向原有属性所指向的对象的内存地址。引用类型是传递引用,指向同一片内存空间,改变一个对象的引用类型的值,另一个对象也会随之改变。但是这里需要注意 String 类型却是一个特殊,String虽然属于引用类型,但是 String 类是不可改变的,它是一个常量,一个对象调用 clone 方法,克隆出一个新对象,这时候两个对象的同一个 String 类型的属性是指向同一片内存空间的,但是如果改变了其中一个,会产生一片新的内存空间,此时该对象的这个属性的引用将指向这片新的内存空间,此时两个对象的String类型的属性指向的就是不同的2片内存空间,改变一个不会影响到另一个,可以当做基本类型来使用。

  • 深克隆

克隆对象的所有属性都会被克隆,不再指向原有对象地址。

2、适用性

该设计模式使用场景很广泛,日常开发中难免有操作某个对象时又不想影响原有对象的情况,具体还需要看业务需求使用浅克隆或者是深克隆。

3、实现逻辑

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

Java 中通过 Object 类中提供的 clone() 方法和 Cloneable 接口来实现浅克隆,clone() 方法由 native 关键字修饰,通过本地方法拷贝地址值来实现,不但效率高还免去我们手动实现的烦恼。 Cloneable 接口充当抽象原型类,而我们实现了 Cloneable 接口的子实现类就是具体的原型类。

需要注意由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。

4、实战代码

4.1 浅克隆

/**
 * 原型类引用类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:29:57
 */
@Setter
@Getter
@ToString
public class Account {
    private String name;
}

/**
 * 具体的原型类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-10-28 17:00:11
 */
@Getter
@Setter
@ToString
public class Member implements Cloneable {

    private int id;

    private String name;

    private Account account;

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

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:34:36
 */
public class ShallowClone {
    public static void main(String[] args) throws CloneNotSupportedException {
        Account account = new Account();
        account.setName("account");

        Member member = new Member();
        member.setId(1);
        member.setName("member");
        member.setAccount(account);

        Member cloneMember = member.clone();

        // clone 对象与原对象的比较
        System.out.println(cloneMember == member);
        System.out.println(cloneMember.getId() == member.getId());
        System.out.println(cloneMember.getAccount() == member.getAccount());
        System.out.println(cloneMember.getName() == member.getName());

        // 修改 clone 对象属性
        account.setName("newAccount");
        cloneMember.setId(2);
        cloneMember.setName("newMember");

        // 查看原型对象
        System.out.println(member);
        System.out.println(cloneMember);
    }
}

执行结果:

从结果不难看出,我们得到的 clone 对象是一个新的对象,但是属性的值或者引用地址都是一样的。再修改 clone 对象后,基本类型的属性的值不会跟着修改, String 类型的属性由于自身特性指向了新的地址值,而我们 Account 类的属性,在 clone 对象的值被修改,两个对象的 account 属性都指向同一个内存地址值,所以会被一起修改。我在生产开发中需要注意原型模式对象引用属性谨慎操作。

4.2 深克隆

进行深克隆需要使用对象流。不用实现 Cloneable 接口,注意实现序列化接口 Serializable

/**
 * 原型类引用类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 15:29:57
 */
@Setter
@Getter
@ToString
public class Account implements Serializable {
    private String name;
}

/**
 * 具体的原型类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-10-28 17:00:11
 */
@Getter
@Setter
@ToString
public class Member implements Serializable {

    private int id;

    private String name;

    private Account account;
}

/**
 * 深克隆测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-05 16:26:57
 */
public class DeepClone {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Account account = new Account();
        account.setName("account");

        Member member = new Member();
        member.setId(1);
        member.setName("member");
        member.setAccount(account);

        //创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("XXX/member.txt"));
        //将c1对象写出到文件中
        oos.writeObject(member);
        oos.close();

        //创建对象出入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("XXX/member.txt"));
        //读取对象
        Member cloneMember = (Member) ois.readObject();
        //获取c2奖状所属学生对象
        Account newAccount = cloneMember.getAccount();
        newAccount.setName("newAccount");

        // 查看原型对象
        System.out.println(member);
        System.out.println(cloneMember);
    }
}

执行结果:

通过序列化和反序列化得到的对象地址值不同来达到目的

相关文章: