设计模式之【原型模式】,深入理解深拷贝与浅拷贝

一、什么是原型模式

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

对于熟悉 JavaScript 语言的前端程序员来说,原型模式是一种比较常用的开发模式。这是因为,有别于 Java、C++ 等基于类的面向对象编程语言,JavaScript 是一种基于原型的面向对象编程语言。即便 JavaScript 现在也引入了类的概念,但它也只是基于原型的语法糖而已。不过,如果你熟悉的是 Java、C++ 等这些编程语言,那在实际的开发中,就很少用到原型模式了。

二、原型模式实现方式

1、传统方式

public class Student {
    
    
    private String name;
    private int age;
	
	// get set
}

public static void main(String[] args) {
    
    
    //传统的方法
    Student student = new Student("tom", 1);
    
    Student student2 = new Student(student.getName(), student.getAge());
    Student student3 = new Student(student.getName(), student.getAge());
    //....

    System.out.println(student2);
    System.out.println(student3);
    //...
}

传统的方式的优缺点:

优点是比较好理解,简单易操作。

在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低

总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活

2、原型模式

原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。

原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()。

熟悉浅拷贝和深拷贝

原型模式主要用于对象的复制,复制的类需要具备以下两个条件:

1)实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException异常。

2)重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,需要拷贝的类需要将clone方法的作用域修改为public类型。

注:Object类的clone方法只会拷贝java中的8中基本类型以及他们的封装类型,另外还有String类型。对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。

浅拷贝实现对象克隆

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值(仅对于简单的值类型数据),而所有的对其他对象的引用都仍然指向原来的对象。换言之,只负责克隆按值传递的数据(比如:基本数据类型、String类型)。

public class Student implements Cloneable {
    
    
    private String name;
    private int age;

    private Address addr;

    public Student(String name, int age, Address addr) {
    
    
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }

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

    public Address getAddr() {
    
    
        return addr;
    }

    public void setAddr(Address addr) {
    
    
        this.addr = addr;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", addr=" + addr +
                '}';
    }

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

class Address implements Cloneable {
    
    
    private String addrName;

    public Address(String addrName) {
    
    
        this.addrName = addrName;
    }

    public String getAddrName() {
    
    
        return addrName;
    }

    public void setAddrName(String addrName) {
    
    
        this.addrName = addrName;
    }

    @Override
    public String toString() {
    
    
        return "Address{" +
                "addrName='" + addrName + '\'' +
                '}';
    }

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

class Test {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        Student student = new Student("zhangsan", 13, new Address("China"));

        Student clone = (Student) student.clone();

        System.out.println(student);
        System.out.println(clone);

        student.setName("lisi");
        student.getAddr().setAddrName("Canada");
        System.out.println(student);
        System.out.println(clone);

    }
}

运行结果:

Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘China’}}
Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘China’}}
Student{name=‘lisi’, age=13, addr=Address{addrName=‘Canada’}}
Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘Canada’}}

我们发现,Java的clone,只是单纯的浅拷贝,对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

这里我们修改了原Student的Address中的值,克隆过的Student中的Address中的值也相应的改变了。

深拷贝实现对象克隆

1、
把上面的Student中clone方法改造一下,即可以实现深拷贝:

@Override
public Object clone() throws CloneNotSupportedException {
    
    
    Student newStu = (Student) super.clone();
    newStu.addr = (Address) this.getAddr().clone();
    return newStu;
}

拷贝的时候,将对象中的引用数据也拷贝一份即可。

2、可以使用序列化 + 反序列化的方式实现深拷贝:

public Object deepCopy(Object object) {
    
    
  ByteArrayOutputStream bo = new ByteArrayOutputStream();
  ObjectOutputStream oo = new ObjectOutputStream(bo);
  oo.writeObject(object);
  
  ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
  ObjectInputStream oi = new ObjectInputStream(bi);
  
  return oi.readObject();
}

当然,序列化反序列化的方式有很多,这里就不一一列举了。

猜你喜欢

转载自blog.csdn.net/A_art_xiang/article/details/128326935
今日推荐