推荐:Java设计模式汇总
原型模式
定义
使用原型实例指定待创建的类型,并且通过复制这个原型实例来创建新的对象。
类型
创建型。
例子
Mail类(邮件类),实现了Cloneable接口
(只有实现了该接口的类的实例才可以调用clone()方法克隆实例,否则会抛出异常)。
package com.kaven.design.pattern.creational.prototype;
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("Mail Class Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
}
Cloneable接口
只是起到告诉程序这个类的实例可以调用clone方法的作用,它本身并没有定义任何方法。
源码如下:
package java.lang;
public interface Cloneable {
}
MailUtil类(邮件的工具类),调用sendMail()方法
可以发送邮件。
package com.kaven.design.pattern.creational.prototype;
import java.text.MessageFormat;
public class MailUtil {
public static void sendMail(Mail mail){
String outputContent = "向 {0} 同学,邮件地址:{1},邮件内容:{2},发送邮件成功";
System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));
}
}
应用层代码:
package com.kaven.design.pattern.creational.prototype;
public class Test {
private static String[] name = {"yy","kaven","jack","jojo"};
public static void main(String[] args) throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("初始化模板");
for (int i = 0; i < name.length; i++) {
Mail mailTemp = (Mail) mail.clone();
mailTemp.setName(name[i]);
mailTemp.setEmailAddress(name[i]+"@lkwyy.com");
mailTemp.setContent("恭喜您,此次活动中奖了");
MailUtil.sendMail(mailTemp);
}
}
}
结果:
Mail Class Constructor
clone mail object
向 yy 同学,邮件地址:yy@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 kaven 同学,邮件地址:kaven@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 jack 同学,邮件地址:jack@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 jojo 同学,邮件地址:jojo@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
Mail mail = new Mail()
会调用构造器创建原型实例,调用构造器时会输出Mail Class Constructor
,而每次要发送的邮件都是对这个原型实例的克隆Mail mailTemp = (Mail) mail.clone();
,并且可以发现clone()方法不会调用构造器
,对象拷贝时构造器是不会被执行的,Object类的clone()方法的原理是从内存中(具体的说,就是堆内存中)以二进制流的方式进行拷贝,并且会重新分配一个内存块(也是堆内存中),也就不需要调用构造器了
。
这里我们便实现了一个简单的原型模式例子,接下来我们分析一下克隆的问题。
深克隆与浅克隆
package com.kaven.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Pig implements Cloneable {
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试1:
package com.kaven.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Pig pig = new Pig("佩奇",new Date(0L));
Pig clonePig = (Pig) pig.clone();
System.out.println(pig);
System.out.println(clonePig);
System.out.println(pig == clonePig);
}
}
输出:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
false
很显然它们不是指向同一个实例,因为会重新分配一个内存块。
测试2:
package com.kaven.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Pig pig = new Pig("佩奇",new Date(0L));
Pig clonePig = (Pig) pig.clone();
System.out.println(pig);
System.out.println(clonePig);
clonePig.getBirthday().setTime(1000000000000L);
System.out.println(pig);
System.out.println(clonePig);
}
}
输出:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
为什么我们改变clonePig的birthday属性,pig的birthday属性也会进行改变?
我们来Debug一下。
打上断点:
开始Debug。
Debug后,可以发现pig(Pig@616)
和clonePig(Pig@617)
确实不是指向同一个实例,但它们的birthday属性却是指向相同的Date实例(Date@621)
,这就是浅克隆,所以,当修改clonePig的birthday属性时,pig的birthday属性也会进行改变。
从上图可以看出,当修改了clonePig的birthday属性时clonePig.getBirthday().setTime(1000000000000L)
,pig的birthday属性也会进行改变。
如何改进成深克隆呢?
改进如下:
package com.kaven.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Pig implements Cloneable {
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Pig clonePig = (Pig) super.clone();
//深克隆
clonePig.birthday = (Date) clonePig.birthday.clone();
return clonePig;
}
}
测试:
package com.kaven.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Pig pig = new Pig("佩奇",new Date(0L));
Pig clonePig = (Pig) pig.clone();
System.out.println(pig);
System.out.println(clonePig);
clonePig.getBirthday().setTime(1000000000000L);
System.out.println(pig);
System.out.println(clonePig);
}
}
结果:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
从测试结果上来看,当修改clonePig的birthday属性时,pig的birthday属性并没有进行改变。
这就改进成深克隆了吗?是的,这便是深克隆。
其实我们还依赖了Date类本身就实现了深克隆
,我们才会如此方便,Date类的clone()方法源码如下:
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
有些引用型没有这种浅克隆的问题,如String或Integer等包装类型
,大家可以自己试一试。
还有就是,final修饰的成员变量是不能进行深度拷贝的
。
适用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
- 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
- 难以根据类生成实例时。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
- 想解耦框架与生成的实例时。
优点
- 原型模式提供了简化的创建结构,常常与工厂方法模式一起使用。
- 可以使用深克隆的方式保存对象的状态,在操作过程中可以追溯操作日志,做撤销和回滚操作。
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
缺点
- 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
- 在做深克隆的时候,如果对象之间存在多重嵌套的引用时,为了实现克隆,对每一层对象对应的类都必须支持深克隆,实现起来比较麻烦。
如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。