场景举例:
由于某些岗位每周工作存在重复性,工作周报内容都大同小异。这些周报只有一些小地方存在差异,但是现行系统每周默认创建的周报都是空白报表,用户只能通过重新输入或不断复制粘贴来填写重复的周报内容,极大降低了工作效率,浪费宝贵的时间。
所以:用户将创建好的周报保存为模板,再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一份相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报
原理说明:
原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
一般而言,Java语言中的clone()方法满足:
(1) 对任何对象x,都有x.clone() != x,即克隆对象与原型对象不是同一个对象;
(2) 对任何对象x,都有x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样;
(3) 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
**********为了获取对象的一份拷贝,我们可以直接利用Object类的clone()方法,具体步骤如下:
(1) 在派生类中覆盖基类的clone()方法,并声明为public;
(2) 在派生类的clone()方法中,调用super.clone();
(3)派生类需实现Cloneable接口。
此时,Object类相当于抽象原型类,所有实现了Cloneable接口的类相当于具体原型类。
浅克隆:
class WeeklyLog implements Cloneable { private String name; private String date; private String content; public void setName(String name) { this.name = name; } public void setDate(String date) { this.date = date; } public void setContent(String content) { this.content = content; } public String getName() { return (this.name); } public String getDate() { return (this.date); } public String getContent() { return (this.content); } //克隆方法clone(),此处使用Java语言提供的克隆机制 public WeeklyLog clone() { Object obj = null; try{ obj = super.clone(); return (WeeklyLog)obj; } catch(CloneNotSupportedException e) { System.out.println("不支持复制!"); return null; } } }
深度克隆:
用clone方法:都实现clonable接口,原型类clone方法中调用引用类型的clone方法。嵌套调用。
***用序列化方法:引用类型成员和原型类都要实现serializable接口。
***总结:序列化更好,如果多层嵌套,clone麻烦。
import java.io.*; //附件类 class Attachment implements Serializable { private String name; //附件名 public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void download() { System.out.println("下载附件,文件名为" + name); } } 工作周报类WeeklyLog不再使用Java自带的克隆机制,而是通过序列化来从头实现对象的深克 隆,我们需要重新编写clone()方法,修改后的代码如下: import java.io.*; //工作周报类 class WeeklyLog implements Serializable { private Attachment attachment; private String name; private String date; private String content; public void setAttachment(Attachment attachment) { this.attachment = attachment; } public void setName(String name) { this.name = name; } public void setDate(String date) { this.date = date; } public void setContent(String content) { this.content = content; } public Attachment getAttachment(){ return (this.attachment); } public String getName() { return (this.name); } public String getDate() { return (this.date); } public String getContent() { return (this.content); } //使用序列化技术实现深克隆 public WeeklyLog deepClone() throws IOException, ClassNotFoundException, { //将对象写入流中 ByteArrayOutputStream bao=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bao); oos.writeObject(this); //将对象从流中取出 ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois=new ObjectInputStream(bis); return (WeeklyLog)ois.readObject(); } } 客户端代码如下所示: class Client { public static void main(String args[]) { WeeklyLog log_previous, log_new = null; log_previous = new WeeklyLog(); //创建原型对象 Attachment attachment = new Attachment(); //创建附件对象 log_previous.setAttachment(attachment); //将附件添加到周报中 try { log_new = log_previous.deepClone(); //调用深克隆方法创建克隆对象的克隆——原型模式(三) } catch(Exception e) { System.err.println("克隆失败!"); } //比较周报 System.out.println("周报是否相同? " + (log_previous == log_new)); //比较附件 System.out.println("附件是否相同? " + (log_previous.getAttachment() } } 编译并运行程序,输出结果如下: 周报是否相同? false 附件是否相同? false
适用场景 在以下情况下可以考虑使用原型模式:
(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修。
(2) 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
(3) 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
练习
设计并实现一个客户类Customer,其中包含一个名为客户地址的成员变量,客户地址的类型为Address,用浅克隆和深克隆分别实现Customer对象的复制并比较这两种克隆方式的异同。