设计模式——原型模式

时间:2024-07-14 08:34:12

设计模式——原型模式

  • 传统模式创建大量对象
    • 代码实现
    • 传统方式的优缺点
  • 原型模式
    • 1.1 基本介绍
    • 1.2 原型模式创建
    • 1.3 深拷贝和浅拷贝
      • 1.3.1 浅拷贝
      • 1.3.1 深拷贝
        • 重写 clone 方法来实现深拷贝
        • 通过对象序列化实现深拷贝:
          • 序列化拷贝的核心代码:
  • 总结

传统模式创建大量对象

代码实现

public class Citation {
    private String name;

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

创建5个对象

public class Client {
    public static void main(String[] args) {
        Citation citation = new Citation();
        Citation citation1 = new Citation();
        Citation citation2 = new Citation();
        Citation citation3 = new Citation();
        Citation citation4 = new Citation();
    }
}

传统方式的优缺点

  1. 优点是比较好理解,简单易操作。
  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

解决思路

思路:Java中Object(object)类是所有类的根类,Object,该方法可以将一个Java对象复制一份,但是需要实现克隆的Java类必须要实现一个接口可克隆(Cloneable),该接口表示该类能够复制且具有复制的能力

原型模式

1.1 基本介绍

  1. 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
  4. 形象的理解:孙大圣拔出猴毛,变出其它孙大圣

一句话解释就是:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

1.2 原型模式创建

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽
象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:

public class Citation implements Cloneable{
    private String name;

    public Citation(String name) {
        this.name = name;
        System.out.println(name);
    }

    public String getName() {
        return name;
    }

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

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

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation("三好学生");
        Citation clone = citation.clone();
    }
}

1.3 深拷贝和浅拷贝

1.3.1 浅拷贝

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

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原
有属性所指向的对象的内存地址。

代码示例:

public class Citation implements Cloneable{
    private String name;

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Citation() {
    }

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

    public String getName() {
        return name;
    }

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

    public void show() {
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Student {
    private String address;
    private String name;

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        citation.setName("三好学生");
        Student student = new Student();
        student.setName("张三");
        citation.setStudent(student);
        Citation clone = (Citation) citation.clone();
        clone.setName("五好学生");
        Student student1 = clone.getStudent();
        student1.setName("李四");
        citation.show();
        clone.show();
        System.out.println(student == student1);
        System.out.println(citation.getStudent());
        System.out.println(clone.getStudent());
    }
}

最后输出的结果

三好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
五好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
true
Student{address='null', name='李四'}
Student{address='null', name='李四'}

说明
stu对象和stu1对象是同一个对象,就会产生将stu1对象中name属性值改为“李四”,两个
Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的
引用类型的属性进行引用的复制。

1.3.1 深拷贝

  1. 复制对象的所有基本数据类型的成员变量值
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
  3. 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
  4. 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)

代码示例:

重写 clone 方法来实现深拷贝

student类实现clone方法,citation类型重写clone方法实现,对引用类型的数据进行处理:

public class Citation implements Cloneable{
    private String name;

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Citation() {
    }

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

    public String getName() {
        return name;
    }

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

    public void show() {
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Citation citation = (Citation) super.clone();
        // 对引用类型 进行clone方法处理
        citation.student = (Student) citation.getStudent().clone();
        return citation;
    }
}

得到的结果是:

三好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
五好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
false
Student{address='null', name='张三'}
Student{address='null', name='李四'}
通过对象序列化实现深拷贝:

类记得实现Serializable接口,不然会报错的,并且属性里面有对象属性也需要实现。

public class Citation implements Cloneable, Serializable {
    private String name;

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Citation() {
    }

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

    public String getName() {
        return name;
    }

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

    public void show() {
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

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

    public Object deepClone(){
        ByteArrayInputStream bis = null;
        ByteArrayOutputStream bos = null;
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try{
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return  ois.readObject();

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Citation citation = (Citation) super.clone();
        // 对引用类型 进行clone方法处理
//        citation.student = (Student) citation.getStudent().clone();
        return citation;
    }
}

public class Student implements Cloneable, Serializable {
    private String address;
    private String name;

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

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

注意,这里调用的是deepClone方法

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        citation.setName("三好学生");
        Student student = new Student();
        student.setName("张三");
        citation.setStudent(student);
        Citation clone = (Citation) citation.deepClone();
        clone.setName("五好学生");
        Student student1 = clone.getStudent();
        student1.setName("李四");
        citation.show();
        clone.show();
        System.out.println(student == student1);
        System.out.println(citation.getStudent());
        System.out.println(clone.getStudent());
    }
}

结果输出:

三好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
五好学生同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
false
Student{address='null', name='张三'}
Student{address='null', name='李四'}

序列化拷贝的核心代码:
public Object deepClone(){
        ByteArrayInputStream bis = null;
        ByteArrayOutputStream bos = null;
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try{
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return  ois.readObject();

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

总结

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深克隆的时候可能需要比较复杂的代码
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,这点请同学们注意