首页 > 学院 > 开发设计 > 正文

原型模式

2019-11-06 08:56:29
字体:
来源:转载
供稿:网友

1.原型模式概述

在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。

原型模式的工作原理:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。由于软件系统中我们经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在真实开发中的使用频率还是非常高的。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过不同的方式修改可以得到一系列相似但不完全相同的对象。

2.原型模式结构

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

抽象原型类(PRototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至可以是具体实现类。

具体原型类(ConcretePrototype):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

客户类(Client):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

通用实现方法

通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同。示意代码如下:

class ConcretePrototype implements Prototype { private String attr; public void setAttr(String attr){ this.attr = attr; } public String getAttr(){ return this.attr; } public Prototype clone(){ Prototype prototype = new ConcretePrototype(); prototype.setAttr(attr); return prototype; }}

java语言提供的clone()方法

学过Java语言的人都知道,所有的Java类都继承自java.lang.Object。事实上,Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。

需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出异常。如下代码所示:

class ConcretePrototype implements Cloneable { ...... public Prototype clone(){ Object object = null; try { object = super.clone(); } catch(ClassNotSupportedException exception){ System.err.println("Not support cloneable"); } return (Prototype)object; } ......}

在客户端创建原型对象和克隆对象也很简单,如下代码所示:

Prototype obj1 = new ConcretePrototype();Prototype obj2 = obj1.clone();

3.原型模式实例

Sunny公司开发人员决定使用原型模式来实现工作周报的快速创建,快速创建工作周报结构图如下所示:

工程目录结构

实现代码

具体原型类

package cn.red.prototype;public class WeekLog implements Cloneable { private String name; private String date; private String content; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public WeekLog clone(){ Object obj = null; try { obj = super.clone(); return (WeekLog) obj; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } }}

客户端测试代码:

package cn.red.prototype;public class Client { public static void main(String[] args) { WeekLog pre_log = new WeekLog(); pre_log.setName("Tom"); pre_log.setDate("第1周"); pre_log.setContent("这种工作很忙,每天加班!"); System.out.println("****周报****"); System.out.println("姓名:" + pre_log.getName()); System.out.println("周次:" + pre_log.getDate()); System.out.println("内容:" + pre_log.getContent()); System.out.println("------------------------"); WeekLog new_log; new_log = pre_log.clone(); new_log.setDate("第2周"); System.out.println("****周报****"); System.out.println("姓名:" + new_log.getName()); System.out.println("周次:" + new_log.getDate()); System.out.println("内容:" + new_log.getContent()); }}

输出结果:

姓名:Tom周 次:第1周内容:这种工作很忙,每天加班!------------------------****周报****姓名:Tom周次:第2周内容:这种工作很忙,每天加班!

3.两种不同的克隆方法

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。

1.浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简言之,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

浅克隆示意图:

在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。为了让大家更好地理解浅克隆和深克隆的区别,我们首先使用浅克隆来实现工作周报和附件类的复制,其结构如下图所示:

工程目录结构:

附件类:

package cn.red.prototype;public class Attachment { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void download() { System.out.println("下载附件,文件名为:" + name); }}

工作周报类:

package cn.red.prototype;public class WeekLog implements Cloneable { private Attachment attachment; private String name; private String date; private String content; public Attachment getAttachment() { return attachment; } public void setAttachment(Attachment attachment) { this.attachment = attachment; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } //使用clone()方法实现浅克隆复制 public WeekLog clone() { Object obj = null; try { obj = super.clone(); return (WeekLog) obj; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } }}

客户端代码如下所示:

package cn.red.prototype;public class Client { public static void main(String[] args) { WeekLog log_previous,log_new; //创建原型对象 log_previous = new WeekLog(); //创建附件对象 Attachment attachment = new Attachment(); //将附件添加到周报中 log_previous.setAttachment(attachment); //调用克隆方法克隆对象 log_new = log_previous.clone(); //比较周报 System.out.println("周报是否相同?" + (log_previous==log_new)); //比较附件 System.out.println("附件是否相同?" + (log_previous.getAttachment()==log_new.getAttachment())); }}

输出结果:

周报是否相同?false附件是否相同?true

由于使用的是浅克隆技术,因此工作周报对象复制成功,通过”==”比较原型对象和克隆对象的内存地址时输出false;但是比较附件对象的内存地址时输出true,说明它们在内存中是同一个对象。

2.深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简言之,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流中将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。下面我们使用深克隆技术来实现工作周报和附件对象的复制,由于要将附件对象和工作周报对象都写入流中,因此两个类均需要实现Serializable接口,其结构如图所示:

附件类:

package cn.red.prototype;import java.io.Serializable;public class Attachment implements Serializable { private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void download() { System.out.println("下载附件,文件名为:" + name); }}

工作周报类:

package cn.red.prototype;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class WeekLog implements Serializable { private static final long serialVersionUID = 1L; private Attachment attachment; private String name; private String date; private String content; public Attachment getAttachment() { return attachment; } public void setAttachment(Attachment attachment) { this.attachment = attachment; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } //使用deepClone()方法实现深克隆复制 public WeekLog 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 (WeekLog) ois.readObject(); }}

客户端代码如下所示:

package cn.red.prototype;public class Client { public static void main(String[] args) { WeekLog log_previous,log_new = null; //创建原型对象 log_previous = new WeekLog(); //创建附件对象 Attachment attachment = new Attachment(); //将附件添加到周报中 log_previous.setAttachment(attachment); //调用克隆方法克隆对象 try { log_new = log_previous.deepClone(); } catch (Exception e) { e.printStackTrace(); } //比较周报 System.out.println("周报是否相同?" + (log_previous==log_new)); //比较附件 System.out.println("附件是否相同?" + (log_previous.getAttachment()==log_new.getAttachment())); }}

输出结果:

周报是否相同?false附件是否相同?false

从输出结果可以看出,由于使用了深克隆技术,附件对象也得以复制,因此用”==”比较原型对象的附件和克隆对象的附件时输出结果均为false。深克隆技术实现了原型对象和克隆对象的完全独立,对任意克隆对象的修改都不会给其他对象产生影响,是一种更为理想的克隆实现方式。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表