原型模式与深浅拷贝

1. 原型模式

原型模式:用于创建重复的对象同时又能保证性能。属于创建型设计模式的范畴,它提供了一种创建对象的最佳方式。

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

2. 克隆羊问题

2.1、传统创建克隆对象
class Sheep{
    private String name;
    private int age;
    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }

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

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

    @Override
    public String toString() {
        return "Sheep{" + "name=" + name + ", age=" + age + "}";
    }
}
public class ProtoType01 {
    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("亨利",2);
        Sheep sheep2 = new Sheep(sheep1.getName(),sheep1.getAge());
        Sheep sheep3 = new Sheep(sheep1.getName(),sheep1.getAge());
        Sheep sheep4 = new Sheep(sheep1.getName(),sheep1.getAge());
        Sheep sheep5 = new Sheep(sheep1.getName(),sheep1.getAge());


        System.out.println("sheep1 = " + sheep1.toString());
        System.out.println("sheep2 = " + sheep2.toString());
        System.out.println("sheep3 = " + sheep3.toString());
        System.out.println("sheep4 = " + sheep4.toString());
        System.out.println("sheep5 = " + sheep5.toString());

    }
}
  1. 优点:简单易操作、容易理解。

  2. 缺点:

    1. 在创建新的对象时、需要重新获取原始对象的属性,如果创建的对象比较复杂时效率较低,因为需要不断地get方法调用获取属性值

    2. 需要重新初始化对象,而且不能动态的获得对象的运行状态,灵活读低。


2.2、原型模式创建

通过实现Cloneable接口,并且重写clone方法;最后调用.clone完成克隆。

class Sheep implements Cloneable{
    private String name;
    private int age;
    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }

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

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

    @Override
    protected Object clone(){
        Sheep sheep = null;
        try{
            sheep = (Sheep) super.clone();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }

    @Override
    public String toString() {
        return "Sheep{" + "name=" + name + ", age=" + age + "}";
    }
}
public class ProtoType02 {
    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("亨利",2);

        Sheep sheep2 = (Sheep) sheep1.clone();
        Sheep sheep3 = (Sheep) sheep1.clone();
        Sheep sheep4 = (Sheep) sheep1.clone();
        Sheep sheep5 = (Sheep) sheep1.clone();
        
        System.out.println("sheep1 = " + sheep1.toString());
        System.out.println("sheep2 = " + sheep2.toString());
        System.out.println("sheep3 = " + sheep3.toString());
        System.out.println("sheep4 = " + sheep4.toString());
        System.out.println("sheep5 = " + sheep5.toString());

    }
}
  1. 优点:性能提高,逃避了构造函数的约束。

  2. 缺点:需要对类的功能进行全局的考虑,必须实现Cloneable接口。


3. 浅拷贝

  1. 对于基本数据类型的成员变量,浅拷贝会直接进行值的传递,换句话说就是将值复制一份然后给新的对象。

  2. 对于引用类型的成员变量,例如:数组、类对象…浅拷贝会进行引用的传递,知识简单的将成员变量的内存地址(引用值)复制一份给新的对象。最后二者都指向同一个内存地址,当一个对象中修改了这个成员变量,另一个对象也会受到影响。

class Cow{
    public String name;
    public int age;

    public Cow(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cow{" + "name=" + name + ", age=" + age + "}";
    }
}

class Sheep implements Cloneable{
    public String name;
    public int age;
    public Cow friend;

    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone(){
        Sheep sheep = null;
        try{
            sheep = (Sheep) super.clone();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }

    @Override
    public String toString() {
        return "Sheep{" + "name='" + name + '\'' + ", age=" + age + ", friend=" + friend + '}';
    }
}
public class Shallow {

    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("亨利",2);
        Cow cow = new Cow("玛丽",5);
        sheep1.friend = cow;
        Sheep sheep2 = (Sheep) sheep1.clone();
        Sheep sheep3 = (Sheep) sheep1.clone();


        System.out.println("sheep1.hashCode(): " + sheep1.hashCode() + ",  sheep1 -> friend: " + sheep1.friend.hashCode());
        System.out.println(sheep1.toString());
        System.out.println("-------------------------------------------------------------------");
        System.out.println("sheep2.hashCode(): " + sheep2.hashCode() + ",  sheep2 -> friend: " + sheep2.friend.hashCode());
        System.out.println(sheep2.toString());
        System.out.println("-------------------------------------------------------------------");
        System.out.println("sheep3.hashCode(): " + sheep3.hashCode() + ",  sheep3 -> friend: " + sheep3.friend.hashCode());
        System.out.println(sheep3.toString());

        System.out.println("\n\n\n\n");

        sheep1.friend.name = "杰克";         //改变friend的属性
        sheep1.friend.age = 1;
        System.out.println("sheep1.hashCode(): " + sheep1.hashCode() + ",  sheep1 -> friend: " + sheep1.friend.hashCode());
        System.out.println(sheep1.toString());
        System.out.println("-------------------------------------------------------------------");
        System.out.println("sheep2.hashCode(): " + sheep2.hashCode() + ",  sheep2 -> friend: " + sheep2.friend.hashCode());
        System.out.println(sheep2.toString());
        System.out.println("-------------------------------------------------------------------");
        System.out.println("sheep3.hashCode(): " + sheep3.hashCode() + ",  sheep3 -> friend: " + sheep3.friend.hashCode());
        System.out.println(sheep3.toString());

    }
}

在这里插入图片描述

在这里插入图片描述


4. 深拷贝

  1. 基本数据类型:复制对象的所有基本数据类型的成员变量值

  2. 引用类型:为所有的引用类型的成员变量申请空间,同时将引用对象的数据赋值到新的空间中。

  3. 深拷贝的两种实现:重写clone方法 或者 通过序列化。

4.1、重写clone方法
  1. 先创建一个sheep变量,并且调用super. clone方法对基本数据类型进行拷贝

  2. 创建引用类型的成员变量,该成员变量调用sheep.成员.clone进行对引用类型的变量深拷贝。

  3. 需要注意的是,如果引用类型内部也有引用类型,引用类型也需要进行深拷贝;例如:A 有 引用B,B有引用C。C必须进行深拷贝、然后B进行深拷贝、最后A进行深拷贝。

  4. 自身类也需要实现Cloneable接口,所有的引用类型都需要实现Cloneable接口,这里的所有也包括引用中的引用。

class Cow implements Cloneable{
    public String name;
    public int age;

    public Cow(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() {
        //只有基本数据类型 和 String, 直接浅拷贝即可。
        Cow cow = null;
        try{
            cow = (Cow) super.clone();
        } catch (Exception e){
            e.printStackTrace();
        }
        return cow;
    }
}

class Sheep implements Cloneable{
    public String name;
    public int age;
    public Cow friend;

    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone(){
        Sheep sheep = null;
        try{
            sheep = (Sheep) super.clone();          //浅拷贝基本数据类型
            Cow cow = (Cow) sheep.friend.clone();   //调用引用类型的成员变量的clone方法进行拷贝
            sheep.friend = cow;                     //替换
        } catch(Exception e) {
            e.printStackTrace();
        }
        return sheep;
    }

}
public class Deep {
    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("山羊", 2);
        sheep1.friend = new Cow("奶牛",2);
        
        Sheep sheep2 = (Sheep) sheep1.clone();

        System.out.println("sheep1.hashCode = " + sheep1.hashCode() + ", sheep1.friend.hashCode =  " + sheep1.friend.hashCode());
        System.out.println("sheep2.hashCode = " + sheep2.hashCode() + ", sheep2.friend.hashCode =  " + sheep2.friend.hashCode());
    }
}

在这里插入图片描述

注意:需要注意的就是引用类型内部如果也有引用类型,也需要进行深拷贝!


4.2、通过序列化(推荐使用)
  1. 序列化:将数据对象转为字节序列的过程

  2. 反序列化:将字节序列重新转化为对象的过程。

  3. 序列化需要使用的两个类:ObjectOutputStream类用于序列化、ObjectInputStream用于反序列化。

  4. 由于序列化(反序列化)不一定需要将对象写入到磁盘(读入),因此可以使用ByteArrayInputStream 和 ByteArrayOutputStream。

  5. 首先使用ObjectOutputStream类将对象写入到ByteArrayOutputStream中,然后将ByteArrayOutputStream中的字节序列递交给ByteArrayInputStream,最后使用ObjectInputStream将ByteArrayInputStream中的字节序列重新反转为一个对象。

在这里插入图片描述

import java.io.*;

class Cow implements Serializable {
    public String name;
    public int age;

    public Cow(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Sheep implements Serializable{
    public String name;
    public int age;
    public Cow friend;

    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Sheep deepCopy(){
        Sheep sheep = null;
        ObjectOutputStream oos = null;
        ByteArrayOutputStream bos = null;

        ObjectInputStream ois = null;
        ByteArrayInputStream bis = null;
        try{
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            sheep = (Sheep)ois.readObject();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sheep;
    }
}
public class Serial {
    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("小羊",2);
        sheep1.friend = new Cow("奶牛",2);

        Sheep sheep2 = sheep1.deepCopy();

        System.out.println("sheep1.hashCode = " + sheep1.hashCode() + ", sheep1.friend.hashCode =  " + sheep1.friend.hashCode());
        System.out.println("sheep2.hashCode = " + sheep2.hashCode() + ", sheep2.friend.hashCode =  " + sheep2.friend.hashCode());
    }
}

在这里插入图片描述

注意:实现序列化的对象,包括引用类都需要实现Serializable接口!

其他的注意事项点击这里:点我


5. 总结

优点:

  • 创建对象比较复杂时,利用原型模型简化对象的创建过程,提高开发效率。

  • 不用重新初始化对象,而是动态获取;当原始对象增加或者减少属性时,其克隆对象也会发生相应的变化,无需修改代码。

缺点:

  • 需要为每一个类都实现克隆接口,并且重写克隆方法!

  • 当对已经存在的类进行改变时需要修改源代码,违背了开闭原则(OCP);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值