原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(引自《大话设计模式》)
这里举例说明一下。比如说现在我要复制一本书。首先,我定义了这本书的相关属性,有书名、价格、作者(一本书可以有多个作者)。
package main.mode.yxms;
import java.util.ArrayList;
public class Book {
private String title;//书名
private int price;//价格
private ArrayList authors = new ArrayList();
public void show() {
System.out.println("title:"+title);
String s = "";
for (String author : authors) {
s = s.concat(author);
s = s.concat("、");
}
System.out.println("authors:"+s.substring(0,s.length()-1));
System.out.println("price:"+price);
System.out.println("----------华丽丽的分割线----------");
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public ArrayList getAuthors() {
return authors;
}
public void addAuthors(String author) {
this.authors.add(author);
}
}
这里注意的是,为了方便list赋值,我将list的set方法改为add方法。
好了,我现在要复制书了,复制之前我当然要先创建一本书,然后进行复制,现在我能想到两种复制方式,一是new,二是=。先说new。
package main.mode.yxms;
public class Test {
public static void main(String[] args) {
//创建一本书
Book book1 = new Book();
book1.setTitle("书名1");
book1.setPrice(10);
book1.addAuthors("作者1");
book1.addAuthors("作者2");
book1.show();
//复制一本书
Book book2 = new Book();
book2.setTitle("书名1");
book2.setPrice(10);
book2.addAuthors("作者1");
book2.addAuthors("作者2");
book2.show();
}
}
结果如下
可以看出,复制的book2和book1一模一样。
再来说一下第二种方式,=。
package main.mode.yxms;
public class Test {
public static void main(String[] args) {
//创建一本书
Book book1 = new Book();
book1.setTitle("书名1");
book1.setPrice(10);
book1.addAuthors("作者1");
book1.addAuthors("作者2");
book1.show();
//复制一本书
Book book2 = new Book();
book2 = book1;
book2.show();
}
}
结果同上
区别在哪呢,当采用方式二时,我们只是复制了book1的引用,如果我们改变book1,book2也会随之改变。
package main.mode.yxms;
public class Test {
public static void main(String[] args) {
//创建一本书
Book book1 = new Book();
book1.setTitle("书名1");
book1.setPrice(10);
book1.addAuthors("作者1");
book1.addAuthors("作者2");
book1.show();
//复制一本书
Book book2 = new Book();
book2 = book1;
book2.show();
book1.setPrice(110);
book1.show();
book2.show();
}
}
结果变为
这里多说一句,如果是book2修改了内容(如book1.setPrice(110);改为book2.setPrice(110);结果同上)
现在来说说这两种方式的缺点:
方式一,比较麻烦,如果book的属性特别多,我们在复制的时候也要写很多的set方法,代码冗余。好处是book2“复制”的是实际内容,book1改变内容时,book2不受影响。
方式二,代码简便,但复制的不是实际内容而是引用。这就相当于book2其实是一本空书,里面都是白页,如果想看内容,它告诉要去book1看一样。
如此看来,我们可以有一个更好的办法去实现“复制”这项功能。就是本文所说的模型模式。
使用场景:
(1)当源对象属性较多时。
(2)需要多个拷贝对象,各个拷贝对象需要修改部分值时,可以保障源对象的值不变。
Prototype:原型类,声明一个克隆自身的接口。在Java中相当于Cloneable接口。
ConcretePrototype:具体的原型类,实现一个克隆自身的方法。
Client:客户端调用,让一个原型克隆自身从而创建一个新的对象。
这里简单说下Cloneable接口。模型模式的核心就是clone()方法。大家应该知道这是Object的方法,如果打开Object源码查看,可以看到clone()方法定义,
protected native Object clone() throws CloneNotSupportedException;
Object类是所有类的父类,即每个类都默认继承了Object类。既然如此,为什么这里还要用Cloneable呢?Cloneable是Java提供的一个接口,用来标示这个对象是可以拷贝的。查看Cloneable的源码,可以看到这个接口中一个方法都没有,它只是起到一个标记作用。在jvm中只有这个标记的对象才有可能被拷贝。只是有可能,一个对象要是可以被拷贝,需要重写clone()方法。即
@Override
public Book clone() {
......
}
这里强调一点,对象拷贝时,类的构造函数是不会被执行的。
举个例子,一目了然。
public class ConstructorTest implements Cloneable{
public ConstructorTest() {
System.out.println("进来构造函数了~~~");
}
public static void main(String[] args) {
ConstructorTest c = new ConstructorTest();
try {
c.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
结果
可以看出,构造函数只被执行了一次,应该是new的时候执行的,clone的时候并没有被执行。
从底层原理来说,Object类的clone方法是从内存中(具体的说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数自然就不会被执行了。(构造函数既然是方法,应该是存在方法区中的。)
就上面那个例子,我要复制一本书。可以用Object中的clone()方法进行拷贝。这里对Book类进行简单修改,增加clone方法。
package main.mode.yxms;
import java.util.ArrayList;
public class Book implements Cloneable {
private String title;//书名
private ArrayList authors = new ArrayList();//作者
private int price;//价格
/**
* 重写拷贝方法
*/
@Override
protected Book clone() {
try {
Book book = (Book) super.clone();
return book;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void show() {
System.out.println("title:"+title);
String s = "";
for (String author : authors) {
s = s.concat(author);
s = s.concat("、");
}
System.out.println("authors:"+s.substring(0,s.length()-1));
System.out.println("price:"+price);
System.out.println("----------华丽丽的分割线----------");
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public ArrayList getAuthors() {
return authors;
}
public void addAuthors(String author) {
this.authors.add(author);
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
客户端测试
package main.mode.yxms;
public class PrototypeTest {
public static void main(String[] args) {
//创建一本书
Book book1 = new Book();
book1.setTitle("书名1");
book1.addAuthors("作者1");
book1.setPrice(10);
book1.show();
//复印一本书
Book book2 = new Book();
book2 = book1.clone();
book2.show();
//修改book2
book2.setTitle("书名2");
book2.setPrice(12);
book2.addAuthors("作者2");
book2.show();
//此时book1
book1.show();
}
}
结果
从结果可以看出,采用clone方法,book2可以完全复制book1的全部内容。当修改book2的内容时,我们发现,title和price更改不会改变book1的内容,但authors的更改却改变了book1的内容。这又为什么呢?
这里涉及一个新的问题——浅拷贝与深拷贝。
Object类提供的clone方法,只是拷贝对象的基本的数据类型(也包括String),对于对象内部的数组、集合、引用对象等都不拷贝,而是指向原生对象内部元素的地址,这种拷贝就叫做浅拷贝。例如上面的例子。
如上图所示,book2在拷贝book1时,将title和price的值也拷贝了,但authors只是拷贝了引用地址,指向的内容区域和book1实际上是统一区域,此时如果book1或book2任一方改变了值,另一方也就变了。
这就是浅拷贝。如何改善呢?就是采用深拷贝。
代码做如下修改
package main.mode.yxms;
import java.util.ArrayList;
public class Book implements Cloneable {
private String title;//书名
private ArrayList authors = new ArrayList();//作者
private int price;//价格
/**
* 重写拷贝方法
*/
@Override
protected Book clone() {
try {
Book book = (Book) super.clone();
// 手动对authors对象也调用clone()函数,进行拷贝
book.authors = (ArrayList) this.authors.clone();
return book;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void show() {
System.out.println("title:"+title);
String s = "";
for (String author : authors) {
s = s.concat(author);
s = s.concat("、");
}
System.out.println("authors:"+s.substring(0,s.length()-1));
System.out.println("price:"+price);
System.out.println("----------华丽丽的分割线----------");
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public ArrayList getAuthors() {
return authors;
}
public void addAuthors(String author) {
this.authors.add(author);
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
客户端测试(同上面的测试类)
package main.mode.yxms;
public class PrototypeTest {
public static void main(String[] args) {
//创建一本书
Book book1 = new Book();
book1.setTitle("书名1");
book1.addAuthors("作者1");
book1.setPrice(10);
book1.show();
//复印一本书
Book book2 = new Book();
book2 = book1.clone();
book2.show();
//修改book2
book2.setTitle("书名2");
book2.setPrice(12);
book2.addAuthors("作者2");
book2.show();
//此时book1并不受影响
book1.show();
}
}
结果
可以看出,book1不受影响,这就是深拷贝。
优点:
(1)可以保障原生对象的完整性,即初始化信息不发生改变。
(2)隐藏了对象创建的细节。
(3)大多数情况下,提高了性能。
缺点:
(1)当原生对象的属性较少时,new效率可能会更高。需要考虑实际业务情况使用原型模式。
(2)构造函数不会被拷贝。
最后一句话总结,原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
说在后面:
本文主要是小猫看《大话设计模式》的笔记式的记录,方便以后查阅。
当然了,我也阅读了许多其它博友相关的博文,再加之自己的理解整理出本文,仅供参考。
参考:
《大话设计模式》
设计模式之原型模式 (这个博友写的很棒,大家可以看看)
23中设计模式之_原型模式(深/浅拷贝) (这个博友写的也不错哦,大家参考参考)