一、基本概念
- 定义
**原型模式(Prototype Pattern)**是一种对象创建型模式,一种基于现有对象模板,通过克隆方式创建对象的模式。
-
模式结构
-
ProtoType(抽象原型类):定义具有克隆自己的方法的接口,是具体原型类的公共父类,接口或抽象类。
-
ConcretePrototype(具体原型类):实现具体的克隆方法,在方法中返回自己的一个克隆对象。
-
Client(客户类):让一个原型克隆自身,从而创建一个新的对象。
二、模式分析
Object类中提供能clone()方法,可以直接使用clone()方法来克隆对象,但是实现克隆的Java类必须实现一个Cloneable接口,表示该类是支持复制的,否则会抛出CloneNotSupportedException异常。
值得注意的是,使用clone方法创建的新对象的构造函数是不会被执行的,也就是说会绕过任何构造函数(有参和无参),因为clone方法的原理是从堆内存中以二进制流的方式进行拷贝,直接分配一块新内存。
比如下面的例子,具体原型类(蓝色类)实现/继承 抽象原型类(接口),再通过Object类中的clone()方法复制一份蓝色对象。
package com.color;
public class Blue implements MyColor {
public Object clone(){
Blue b = null;
try {
b = (Blue)super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return b;
}
public void display(){
System.out.println("This is Blue!");
}
}
三、浅克隆与深克隆
举一个简单的例子,孙悟空能根据自己的猴毛复制很多个孙悟空。当一个孙悟空在抓头发的时候,所有的对于孙悟空对象的引用都指向原始对象,并且同时在抓头发,这就是浅克隆。反之复制了属性,新复制的属性指向新的孙悟空(进行重写原有方法),从而表现有不同的动作,则为深克隆。
一封邮件简化为有名字、等级等成员属性和下载附件的方法,复制的时候没有包含了引用对象的下载附件的方法就为浅克隆,反之就为深克隆。
浅克隆
浅克隆中,被复制对象的所有普通成员变量都具有原来对象的相同的值,而所有对其他的引用都仍然指向原来的对象。
即当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而应用类型的成员变量并没有复制。
代码示例
-
类图
-
Attachment附件类
package com.email;
public class Attachment {
public void download(){
System.out.println("下载附件!");
}
}
- Email邮件类(具体原型类)
package work.email;
public class Email implements Cloneable{
private String emailTitle;
private int emailLevel;
public String getEmailTitle() {
return emailTitle;
}
public void setEmailTitle(String emailTitle) {
this.emailTitle = emailTitle;
}
public int getEmailLevel() {
return emailLevel;
}
public void setEmailLevel(int emailLevel) {
this.emailLevel = emailLevel;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
//new一个空的附件对象
private Attachment attachment = null;
public Email() {
this.attachment = new Attachment();
}
public Object clone(){
Email clone = null;
try {
clone = (Email)super.clone();
}catch (CloneNotSupportedException e)
{
e.printStackTrace();
System.out.println("clone failure!");
}
return clone;
}
public Attachment getAttachment(){
return attachment;
}
public void display(){
System.out.println("查看邮件");
}
}
- Client客户端
package work.email;
public class Client {
public static void main(String[] args) {
Email email,copyEmail;
email = new Email();
email.setEmailLevel(5);
email.setEmailTitle("qq邮件");
copyEmail = (Email)email.clone();
copyEmail.setEmailTitle("网易邮件");
copyEmail.setEmailLevel(3);
System.out.println("邮件名字:"+email.getEmailTitle());
System.out.println("邮件等级:"+email.getEmailLevel());
System.out.println();
System.out.println("邮件名字:"+copyEmail.getEmailTitle());
System.out.println("邮件等级:"+copyEmail.getEmailLevel());
System.out.println("-------------------------------------------");
System.out.println("email == copyEmail ?");
System.out.println(email == copyEmail);
System.out.println("email.attchment == copyEmail.attachment ?");
System.out.println(email.getAttachment() == copyEmail.getAttachment());
//email.attchment == copyEmail.attachment表示同时复制了对象的引用
}
}
- 输出结果
如果不给复制后的新对象赋新的值的话,复制后的对象属性与原始对象相同(具有相同的EmailLevel和EmailName),这说明新对象与原始对象是共用nameList的这个成员变量的,这就是浅拷贝。
深克隆
在深克隆中,被复制对象的所有普通成员变量也都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
即除了对象本身被复制外,对象所包含的所有成员变量将被复制,复制过程通过实现Serializable读取二进制流。
代码示例
-
类图
-
Attachment附件类
package deepemail;
import java.io.Serializable;
public class Attachment implements Serializable {
public void download(){
System.out.println("下载附件!");
}
}
- Email(邮件)具体原型类
package deepemail;
import java.io.*;
public class Email {
//创建附件
private Attachment attachment = null;
private String emailTitle;
private int emailLevel;
public Email() {
this.attachment = new Attachment();
}
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getEmailTitle() {
return emailTitle;
}
public void setEmailTitle(String emailTitle) {
this.emailTitle = emailTitle;
}
public int getEmailLevel() {
return emailLevel;
}
public void setEmailLevel(int emailLevel) {
this.emailLevel = emailLevel;
}
public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException {
//将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(bao);
oss.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ois.readObject());
}
public void display(){
System.out.println("查看邮件");
}
}
- Client客户端
package deepemail;
public class Client {
public static void main(String[] args) {
Email email , copyEmail = null;
email = new Email();
email.setEmailLevel(5);
email.setEmailTitle("qq邮件");
try {
copyEmail = (Email)email.deepClone();
System.out.println("email邮件名字:"+email.getEmailTitle());
System.out.println("copyEmail邮件名字:"+copyEmail.getEmailTitle());
System.out.println("-------------------------------------------");
}catch (Exception e)
{
e.printStackTrace();
}
//打印原对象与克隆对象是否为同一对象
System.out.println("email == copyEmail ?");
System.out.println(email == copyEmail);
//打印原对象与克隆对象的附件啊是否为同一附件
System.out.println("email.attachment == copy.attachment ?");
System.out.println(email.getAttachment() == copyEmail.getAttachment());
}
}
- 输出结果
email.getAttachment() == copyEmail.getAttachment() 输出为false表示复制后的对象不再指向原有的对象。
优缺点
优点
- 简化对象的创建过程,通过复制一个已有实例可以提高实例的创建效率
- 扩展性较好
- 简化创建结构,复制通过封装在原型类的克隆方法实现,无需专门工厂
- 可以使用深克隆方式保存对象状态,以便需要时使用,可辅助撤销操作
缺点
- 对象深克隆有时相当麻烦,需要编写较为复杂的代码
- 需要给每一个类配备克隆方法,对原有类进行改造违背“开闭原则”
四、适用环境
- 创建新对象成本较大,相同对象可以直接进行复制,相似对象复制后可以稍作修改。
- 系统需要保存对象的状态,对象变化较小,或者对象本身占内存不大,也可以使用备忘录模式。相反采用状态模式较好。
- 需要避免使用分层次的工厂类创建分层次的对象并且类的实例对象只有一个或者很少的组合状态
参考书籍《JAVA设计模式——刘伟第二版》《设计模式之禅》