设计模式:原型模式

今天介绍原型模式,我本身偷偷给它命名为克隆模式。由于原型模式的意图是经过复制一个现有的对象来生成新的对象,而不是经过实例化的方式。java

1、原型模式概念数据库

     原型模式(Prototype Pattern):使用原型实例指定建立对象的种类,而且经过拷贝这些原型建立新的对象。原型模式是一种对象建立型模式。编程

     该接口用于建立当前对象的克隆。当直接建立对象的代价比较大时,则采用这种模式。例如,一个对象须要在一个高代价的数据库操做以后被建立。咱们能够缓存该对象,在下一个请求时返回它的克隆,在须要的时候更新数据库,以此来减小数据库调用。设计模式

     须要注意的是经过克隆方法所建立的对象是全新的对象,它们在内存中拥有新的地址,一般对克隆所产生的对象进行修改对原型对象不会形成任何影响,每个克隆对象都是相互独立的。经过不一样的方式修改能够获得一系列类似但不彻底相同的对象。数组

2、原型模式结构图缓存

在原型模式结构图中包含以下几个角色:网络

  • 抽象原型角色(Prototype):它是声明克隆方法的接口,是全部具体原型类的公共父类,能够是抽象类也能够是接口,甚至还能够是具体实现类。
  • 具体原型角色(ConcretePrototype):它实如今抽象原型类中声明的克隆方法,在克隆方法中返回本身的一个克隆对象。
  • 客户角色(Client):客户类提出建立对象的请求。

     原型模式的核心在于如何实现克隆方法,博主本人是从事Java开发,因此用Java语言提供的clone()方法来实现。学过Java语言的人都知道,全部的Java类都继承自java.lang.Object。事实上,Object类提供一个clone()方法,能够将一个Java对象复制一份。所以在Java中能够直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。函数

     须要注意的是可以实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。若是一个类没有实现这个接口可是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。this

克隆知足的条件spa

clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的有关。通常而言,clone()方法知足如下的描述:

  • 对任何的对象x,都有:x.clone()!=x 。换言之,克隆对象与元对象不是一个对象
  • 对任何的对象x,都有:x.clone().getClass==x.getClass(),换言之,克隆对象与元对象的类型同样
  • 若是对象x的equals()方法定于恰当的话,那么x.clone().equals(x)应当是成立的。
    在Java语言的API中,凡是提供了clone()方法的类,都知足上面的这些条件。Java语言的设计师在设计本身的clone()方法时,也应当遵照这三个条件。

    在理解Java原型模式以前,首先须要理解Java中的一个概念:复制/克隆。Java中的对象复制/克隆分为浅复制和深复制。在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在因而否支持引用类型的成员变量的复制,下面将对二者进行详细介绍。

3、浅克隆

    在浅克隆中,若是原型对象的成员变量是值类型,将复制一份给克隆对象;若是原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来讲,在浅克隆中,当对象被复制时只复制它自己和其中包含的值类型的成员变量,而引用类型的成员对象并无复制。下面以复制一本书为例。

下面是Author源代码:

public class Author {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

下面是Book源代码:

public class Book  implements Cloneable{
    private String bookName;
    private int price;
    private Author author;

    public Book clone() {
        Book  book=null;
        try {
            book=(Book)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return book;

    }

    public Author getAuthor() {
        return author;
    }

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

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

下面是客户端类:

public class Client {
    public static void main(String[] args){
        Author author=new Author();
        author.setName("tengj");
        Book book=new Book();
        book.setBookName("Java设计模式");
        book.setPrice(99);
        book.setAuthor(author);
        Book book2=book.clone();
        System.out.println(book==book2);                                 // false
        System.out.println(book.getBookName() == book2.getBookName());   // true
        System.out.println(book.getAuthor() == book2.getAuthor());       // true
    }
}

    由输出的结果能够验证说到的结论。由此咱们发现:虽然复制出来的对象从新在堆上开辟了内存空间,可是,对象中各属性确保持相等。对于基本数据类型很好理解,但对于引用数据类型来讲,则意味着此引用类型的属性所指向的对象自己是相同的, 并无从新开辟内存空间存储。换句话说,引用类型的属性所指向的对象并无复制。由此,咱们将其称之为浅复制。当复制后的对象的引用类型的属性所指向的对象也从新得以复制,此时,称之为深复制。

4、深克隆

     在深克隆中,不管原型对象的成员变量是值类型仍是引用类型,都将复制一份给克隆对象,深克隆将原型对象的全部引用对象也复制一份给克隆对象。简单来讲,在深克隆中,除了对象自己被复制外,对象所包含的全部成员变量也将复制。

     在Java语言中,若是须要实现深克隆,能够经过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。经过序列化实现的拷贝不只能够复制对象自己,并且能够复制其引用的成员对象,所以经过序列化将对象写到一个流中,再从流里将其读出来,能够实现深克隆。须要注意的是可以实现序列化的对象其类必须实现Serializable接口,不然没法实现序列化操做

仍是以复制一本书为例。下面是Author源代码(Author也须要实现Serializable接口!!):

public class Author implements Serializable{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

下面是Book源代码(Book类须要实现Serializable接口):

public class Book  implements Serializable{
    private String bookName;
    private int price;
    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 Author getAuthor() {
        return author;
    }

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

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

下面是客户端类:

public class DeepClient {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Author author=new Author();
        author.setName("tengj");
        Book book=new Book();
        book.setBookName("Java设计模式");
        book.setPrice(99);
        book.setAuthor(author);
        Book book2=book.deepClone();
        System.out.println(book==book2);                                 // false
        System.out.println(book.getBookName() == book2.getBookName());   // false
        System.out.println(book.getAuthor() == book2.getAuthor());       // false
    }
}

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

5、总结

    原型模式做为一种快速建立大量相同或类似对象的方式,在软件开发中应用较为普遍,不少软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操做就是原型模式的典型应用,下面对该模式的使用效果和适用状况进行简单的总结。

一、主要优势

原型模式的主要优势以下:

  • 当建立新的对象实例较为复杂时,使用原型模式能够简化对象的建立过程,经过复制一个已有实例能够提升新实例的建立效率。
  • 扩展性较好,因为在原型模式中提供了抽象原型类,在客户端能够针对抽象原型类进行编程,而将具体原型类写在配置文件中,增长或减小产品类对原有系统都没有任何影响。
  • 原型模式提供了简化的建立结构,工厂方法模式经常须要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不须要这样,原型模式中产品的复制是经过封装在原型类中的克隆方法实现的,无须专门的工厂类来建立产品。
  • 可使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在须要的时候使用(如恢复到某一历史状态),可辅助实现撤销操做。

二、主要缺点

原型模式的主要缺点以下:

  • 须要为每个类配备一个克隆方法,并且该克隆方法位于一个类的内部,当对已有的类进行改造时,须要修改源代码,违背了“开闭原则”。
  • 在实现深克隆时须要编写较为复杂的代码,并且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

三、适用场景

在如下状况下能够考虑使用原型模式:

  • 建立新对象成本较大(如初始化须要占用较长的时间,占用太多的CPU资源或网络资源),新的对象能够经过原型模式对已有对象进行复制来得到,若是是类似对象,则能够对其成员变量稍做修改。
  • 若是系统要保存对象的状态,而对象的状态变化很小,或者对象自己占用内存较少时,可使用原型模式配合备忘录模式来实现。
  • 须要避免使用分层次的工厂类来建立分层次的对象,而且类的实例对象只有一个或不多的几个组合状态,经过复制原型对象获得新实例可能比使用构造函数建立一个新实例更加方便。
相关文章
相关标签/搜索