美文网首页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