设计模式之原型(Prototype)
介绍
原型模式是一个创建型的模式。原型二字表明了改模式应该有一个样板实例,用户从这个样板对象中复制一个内部属性一致的对象,这个过程也就是我们称的“克隆”。被复制的实例就是我们所称的“原型”,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。
使用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
- 通过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方法才能够获得效率上的提升。