美文网首页Java 杂谈
设计模式之原型(Prototype)

设计模式之原型(Prototype)

作者: ikonan | 来源:发表于2018-07-27 11:09 被阅读8次

介绍

原型模式是一个创建型的模式。原型二字表明了改模式应该有一个样板实例,用户从这个样板对象中复制一个内部属性一致的对象,这个过程也就是我们称的“克隆”。被复制的实例就是我们所称的“原型”,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

使用场景

  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
  • 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象

UML类图

原型模式UML类图

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。

简单示范(浅复制)

定义Book类和Author类:

public 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;
    }
}

public 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) {
            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;
    }
}

测试

public class PrototypeTest {

    public static void main(String[] args) {

        Book book1 = new Book();
        Author author = new Author();
        author.setName("dsguo");
        author.setAge(29);

        book1.setAuthor(author);
        book1.setTitle("springboot颠覆者开发");
        book1.setPageNum(345);

        Book book2 = book1.clone();

        book2.setTitle("Gof设计模式");
        book2.getAuthor().setName("zhangchao");

        System.out.println(book1==book2);
        System.out.println("book1.pageNum:"+book1.getPageNum());
        System.out.println("book2.pageNum:"+book2.getPageNum());

        System.out.println("book1.title:"+book1.getTitle());
        System.out.println("book2.title:"+book2.getTitle());

        System.out.println("book1.author.name:"+book1.getAuthor().getName());
        System.out.println("book2.author.name:"+book2.getAuthor().getName());
    }

}

运行结果:
false
book1.pageNum:345
book2.pageNum:345
book1.title:springboot颠覆者开发
book2.title:Gof设计模式
book1.author.name:zhangchao
book2.author.name:zhangchao

解释:
细心观察发现,最后两个书本内容输出是一致的。引用类型的新对象book2的author只是单纯指向了this.author引用,并没有重新构造一个author对象,然后将原始书本的author添加到新的author对象中,这样导致book2中的author与原始书本中的是同一个对象。因此,修改其中一个书本的作者,另一个书本也会受到影响。

如何解决?因为Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、集合、容器对象、引用对象等都不会拷贝;所以采用深拷贝。

深拷贝应用

Auhor类也实现Cloneable接口

public class Author implements Cloneable{

    private String name;
    private int age;

    public Author clone() {
        Author author = null;
        try {
            author = (Author)super.clone();
        } catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return author;

    }

    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;
    }
}

Book类clone方法新增代码 book.author = this.author.clone();

public class Book implements Cloneable{

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

    public Book clone() {
        Book book = null;
        try {
            book = (Book)super.clone();
            book.author = this.author.clone();
        } catch(CloneNotSupportedException e) {
            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;
    }
}

运行结果:

false
book1.pageNum:345
book2.pageNum:345
book1.title:springboot颠覆者开发
book2.title:Gof设计模式
book1.author.name:dsguo
book2.author.name:zhangchao

说明:上面的利用Cloneable接口实现拷贝的功能,但是只得注意的是如果拷贝的对象里面存在多个对象或者多级对象,则每个对象都要实现Cloneable接口。逐层的实现要拷贝的内容。下面我们将介绍一种只需要实现Serializable接口然后自定义的一种深度拷贝。

自定义深度复制方法

Java中的深复制一般是通过对象的序列化和反序列化得以实现。序列化时,需要实现Serializable接口。
注意:不仅Book类需要实现Serializable接口,Author同样也需要实现Serializable接口!!

Author类 示例

import java.io.Serializable;

public 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;
    }
}

Book类示例:

public 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;
    }
}

输出结果

false
book1.pageNum:345
book2.pageNum:345
book1.title:springboot颠覆者开发
book2.title:Gof设计模式
book1.author.name:dsguo
book2.author.name:zhangchao

总结

优点

  • 原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。
  • 还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

缺点

  • 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在问题。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
  • 通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。

相关文章

网友评论

    本文标题:设计模式之原型(Prototype)

    本文链接:https://www.haomeiwen.com/subject/cipwmftx.html