原型模式?java深克隆和浅克隆

一、深克隆、浅克隆

1. 用“=”(等于号)进行对象的复制

实际上是Person xiaoming 和Person xiaohong 都只是引用变量 ,它们两个都指向了同一个地址,就是实际的对象的地址,所以不能称之为复制对象:

package com.shine.study.studyclone;

/**
 * @Author: Shine EtherealWind
 * @Description: 使用“=”(等于号)进行对象的复制
 * 实际上是Person xiaoming 和Person xiaohong 都只是引用变量
 * 它们两个都指向了同一个地址,就是实际的对象的地址;
 *
 * 所以不能称之为复制对象
 * @Date: create in 15:22 2022/2/25
 */
public class TestProtoTypeCloneable {
    public static void main(String[] args) throws CloneNotSupportedException {
        Card card1 = new Card("2022022401", "学生证", true);
        Person xiaoming = new Person("小明", 15, "男", "学生", "人大附中","七年级","703班",card1);
        Person xiaohong = xiaoming;
        System.out.println("第一个对象xiaoming地址:"+xiaoming  +" ||| 第二个对象xiaohong地址:"+xiaohong);
        System.out.println("第一个对象xiaoming.card地址:"+xiaoming.getCard()+" ||| 第二个对象xiaohong.card地址:"+xiaohong.getCard());

        System.out.println("修改第二个对象......");
        xiaohong.getCard().setCardNum("2022022402");
        xiaohong.setName("小红");
        xiaohong.setAge(16);
        xiaohong.setSex("女");
        System.out.println("修改后! 第一个对象xiaoming地址:"+xiaoming  +" ||| 第二个对象xiaohong地址:"+xiaohong);
        System.out.println("修改后! 第一个对象xiaoming.getName:" + xiaoming.getName() + " ||| 第二个对象xiaohong.getName:"+xiaohong.getName());
        System.out.println("修改后! 第一个对象xiaoming.card地址:"+xiaoming.getCard()+" ||| 第二个对象xiaohong.card地址:"+xiaohong.getCard());
        System.out.println("修改后! 第一个对象xiaoming.card.getCardNum:"+xiaoming.getCard().getCardNum() + " ||| 第二个对象xiaohong.card.getCardNum:" + xiaohong.getCard().getCardNum() );
    }
}

class Card {
    private String cardNum;
    private String cardType;
    private boolean isValid;

    public Card(String cardNum, String cardType, boolean isValid) {
        this.cardNum = cardNum;
        this.cardType = cardType;
        this.isValid = isValid;
    }

    public String getCardNum() { return cardNum; }
    public void setCardNum(String cardNum) { this.cardNum = cardNum; }
    public String getCardType() { return cardType; }
    public void setCardType(String cardType) { this.cardType = cardType; }
    public boolean isValid() { return isValid; }
    public void setValid(boolean valid) { isValid = valid; }
}

class Person {
    private String name;
    private int age;
    private String sex;
    private String position;
    private String schoolName;
    private String gradeName;
    private String className;
    private Card card;

    public Person(String name, int age, String sex, String position, String schoolName, String gradeName, String className, Card card) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.position = position;
        this.schoolName = schoolName;
        this.gradeName = gradeName;
        this.className = className;
        this.card = card;
    }

    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 String getSex() { return sex; }
    public void setSex(String sex) { this.sex = sex; }
    public String getPosition() { return position; }
    public void setPosition(String position) { this.position = position; }
    public String getSchoolName() { return schoolName; }
    public void setSchoolName(String schoolName) { this.schoolName = schoolName; }
    public String getGradeName() { return gradeName; }
    public void setGradeName(String gradeName) { this.gradeName = gradeName; }
    public String getClassName() { return className; }
    public void setClassName(String className) { this.className = className; }
    public Card getCard() { return card; }
    public void setCard(Card card) { this.card = card; }
}
View Code

运行结果

通过运行结果,可以看出使用“=”(等于号),从对象的属性值和地址可以看出第二个xiaohong实际上和第一个xiaoming是同一个对象 其关联的子类的对象也是同一个

 

 

2. 实现Cloneable接口完成浅克隆

在第一步的基础上,对Person做调整,增加实现Cloneable接口,并重写clone方法;

调用的时候不再使用“=”(等于号),而是通过xiaoming.clone(); 来复制对象。

 

 运行结果

通过结果可以看出,对于实现了Cloneable接口并重写clone方法,采用clone();方法来复制对象,由运行结果里的对象地址和对象的属性值可知获得的新对象和原来的对象不是同一个:

 

 

 3.实现Cloneable接口实现深克隆

从第二步的运行结果,我们可以看到Person类所依赖的Crad类,在生成对象的时候,即使是使用xiaoming.clone();方法,card对象依旧是同一个对象,要想card也是一个新的对象,这就需要深克隆了

在第二步的基础上,将Person类所依赖的Card类也需要改为实现Cloneable接口,并重写clone方法,并且对于Person的clone方法也需要调整一下;(也是如此,实现深克隆的过程是比较麻烦的,就设计模式上来说,是不符合开闭原则的

(1) Card类需实现Cloneable接口,并重写clone方法;

 

 (2)Person类的clone方法调整

 

运行结果

通过运行结果,可以看出Person的对象xiaoming通过克隆得到的xiaohong,两个对象以及其所依赖的类生成的对象,它们属性值和对象地址都不同,可以知道确实是两组不同的对象:

 

 

 4.通过实现序列化接口实现深克隆

 除了实现 Serializable接口实现深克隆,还可以使用io流来实现对象的克隆

package com.shine.study.studyclone;

import java.io.*;

/**
 * @Author: Shine EtherealWind
 * @Description:
 * @Date: create in 14:48 2022/3/7
 */
public class TestProtoTypeSerializable {
    public static void main(String[] args) {
        Paper testPaper = new Paper("b5", "10000");
        Book testBook = new Book("《西游记》", "小说", testPaper);
        System.out.println("===> testBook地址:"+testBook +"\n  ===> testPaper地址:"+testBook.getPaper());

        Book cloneBook = (Book) testBook.clone();
        cloneBook.setBookName("《水浒传》");
        System.out.println("===> cloneBook地址:"+cloneBook+"\n  ===> clonePaper地址:"+cloneBook.getPaper());
    }
}

class Book implements Serializable {
    private static final long serialVersionUID = 7035330038063156302L;
    private String bookName;
    private String bookType;
    private Paper paper;

    public Book(String bookName, String bookType, Paper paper) {
        this.bookName = bookName;
        this.bookType = bookType;
        this.paper = paper;
    }
    public Object clone(){
        Book book = null;
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);
            book = (Book) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (oos!=null) oos.close();
                if (ois!=null) ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return book;
    }
    public String getBookName() { return bookName; }
    public void setBookName(String bookName) { this.bookName = bookName; }
    public String getBookType() { return bookType; }
    public void setBookType(String bookType) { this.bookType = bookType; }
    public Paper getPaper() { return paper; }
    public void setPaper(Paper paper) { this.paper = paper; }
}
class Paper implements Serializable{
    private static final long serialVersionUID = 7005885349323024969L;
    private String paperSize;
    private String paperNum;
    public Paper(String paperSize, String paperNum) {
        this.paperSize = paperSize;
        this.paperNum = paperNum;
    }
    public String getPaperSize() { return paperSize; }
    public void setPaperSize(String paperSize) { this.paperSize = paperSize; }
    public String getPaperNum() { return paperNum; }
    public void setPaperNum(String paperNum) { this.paperNum = paperNum; }
}
View Code

通过实现序列化接口,注意依赖的类也需要实现序列化接口,否则在 I/O 流操作时候会出现不可序列化的异常

运行结果

 

通过运行结果可以看出在克隆的时候,不论使用的对象是依赖的类的对象,都会生成新的对象;

如果没有对所依赖的类的对象实现序列化接口,则会抛出NotSerializableException异常,如图所示:

 

二、原型模式

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

原型模式的优点:

  • Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

原型模式的结构与实现

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

模式的结构

原型模式包含以下主要角色。

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

 

例如本篇的上面的例子,想要创建一个和已存在的对象各个属性基本相似,但又有不同的新对象时,使用克隆要比重新构造一个对象要合适很多,性能上也更优良。

posted @ 2022-02-28 14:04  EtherealWind  阅读(71)  评论(0编辑  收藏  举报