【Java设计模式】:创建型模式—原型模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hxcaifly/article/details/85261720

1.原型模式(Prototype Pattern)

定义
原型(Prototype)模式是一种对象创建型模式,他采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。

原型模式的特点

  1. 由原型对象自身创建目标对象。也就是说,对象创建这一动作发自原型对象本身。
  2. 目标对象是原型对象的一个克隆。也就是说,通过原型模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值。
  3. 根据对象克隆深度层次的不同,有浅度克隆与深度克隆

2.浅复制

我们知道,一个类的定义中包括属性和方法。属性用于表示对象的状态,方法用于表示对象所具有的行为。其中,属性既可以是Java中基本数据类型,也可以是引用类型。Java中的浅复制通常使用clone()方式完成。

当进浅复制时,clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。同时,复制出来的对象具有与原对象一致的状态。

此处对象一致的状态是指:复制出的对象与原对象中的属性值完全相等

下面以复制一本书为例:

1.定义Book类和Author类:

class Author {

    private String name;
    private int age;

    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;
    }
}
class Book implements Cloneable {

    private String title;
    private int pageNum;
    private Author author;

    public Book clone() {
        Book book = null;
        try {
            book = (Book) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return book;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

}

2.测试:

package com.qqyumidi;

public class PrototypeTest {

    public static void main(String[] args) {
        Book book1 = new Book();
        Author author = new Author();
        author.setName("corn");
        author.setAge(100);
        book1.setAuthor(author);
        book1.setTitle("好记性不如烂博客");
        book1.setPageNum(230);

        Book book2 = book1.clone();
        
        System.out.println(book1 == book2);  // false
        System.out.println(book1.getPageNum() == book2.getPageNum());   // true
        System.out.println(book1.getTitle() == book2.getTitle());        // true
        System.out.println(book1.getAuthor() == book2.getAuthor());        // true
        
    }
}

由输出的结果可以验证说到的结论。由此我们发现:虽然复制出来的对象重新在堆上开辟了内存空间,但是,对象中各属性确保持相等。对于基本数据类型很好理解,但对于引用数据类型来说,则意味着此引用类型的属性所指向的对象本身是相同的, 并没有重新开辟内存空间存储。换句话说,引用类型的属性所指向的对象并没有复制。

这样的话两个对象共享了一个私有变量,所有人都可以改,是一个种非常不安全的方式,在实际项目中使用还是比较少的。

由此,我们将其称之为浅复制。当复制后的对象的引用类型的属性所指向的对象也重新得以复制,此时,称之为深复制。

3.深复制

Java中的深复制一般是通过对象的序列化和反序列化得以实现。序列化时,需要实现Serializable接口。

下面还是以Book为例,看下深复制的一般实现过程:

1.定义Book类和Author类(注意:不仅Book类需要实现Serializable接口,Author同样也需要实现Serializable接口!!):

class Author implements Serializable{

    private String name;
    private int age;

    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;
    }
}
class Book implements Serializable {

    private String title;
    private int pageNum;
    private Author author;

    public Book deepClone() throws IOException, ClassNotFoundException{
        // 写入当前对象的二进制流 
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);
        
        // 读出二进制流产生的新对象  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return (Book) ois.readObject();
    }
    
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

}

2.测试:

public class PrototypeTest {

    public static void main(String[] args) throws ClassNotFoundException, IOException {
        Book book1 = new Book();
        Author author = new Author();
        author.setName("corn");
        author.setAge(100);
        book1.setAuthor(author);
        book1.setTitle("好记性不如烂博客");
        book1.setPageNum(230);

        Book book2 = book1.deepClone();
        
        System.out.println(book1 == book2);  // false
        System.out.println(book1.getPageNum() == book2.getPageNum());   // true
        System.out.println(book1.getTitle() == book2.getTitle());        // false
        System.out.println(book1.getAuthor() == book2.getAuthor());        // false
        
    }
}

从输出结果中可以看出,深复制不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。

4. 原型模式的应用场景

最后说一下,原型模式的使用场景

  1. 在创建对象的时候,我们不只是希望被创建的对象继承其基类的基本结构,还希望继承原型对象的数据。
  2. 希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。
  3. 隐藏克隆操作的细节,很多时候,对对象本身的克隆需要涉及到类本身的数据细节。
  4. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;
  5. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  6. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。

猜你喜欢

转载自blog.csdn.net/hxcaifly/article/details/85261720