文档章节

Java设计模式之原型模式

断桥残雪断桥残雪
 断桥残雪断桥残雪
发布于 2015/08/13 21:04
字数 1760
阅读 845
收藏 5

原型模式简介

原型模式实际上不算一种设计模式,应该说是一种技巧吧。当我们需要创建与已有对象一样的对象时,我们通常可以有两种容易想到的方法,一种是将已有对象指向另外一个重新创建的对象,如

//将old赋给new
Object newObject=oldObject;



这种做法是相当于newObject还是指向oldObject的地址,也就是说,二者实际上是一样的,未来也是一样的,随便对哪个对象进行更改,二者都会保持一致,因为可以把它们看做两个相同的“指针”;另外一种常见的做法是,重新创建一个对象,用new来实例化,这样就创建了另外一个对象,即向内存中再写入了一个对象,虽然内容一样,但地址不一样,但是这种做法费力,如果对象比较复杂的话。

原型模式在这种需求下就诞生了,我们知道Object乃一切对象的父类(超类),并且Object有一个原生的clone方法,但是该方法的调用必须要求类实现了Cloneable接口,虽然Cloneable接口只是一个摆设,里面空空荡荡,姑且就当Cloneable接口是clone方法实现的一个标志吧!我们可以创建一个类实现Cloneable即可,在覆写clone方法即可完成该类的克隆了。

原型模式的代码实现

下面写一个Dog类,该类实现了Cloneable接口,并且覆写了超类的clone方法,里面使用了super.clone,表示调用超类的原生代码即可。注意,Dog类有一个非基本数据类型的变量eye,下面会介绍这个点。

浅复制

package com.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 Dog implements Cloneable,Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2050795770781171788L;
	
	private String name;
	
	Eye eye;
	
	public Dog(Eye eye) {
		this.eye=eye;
	}

	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name=name;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Dog dog;
		dog=(Dog) super.clone();
		return dog;
	}    
}
class Eye implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2723012171722328322L;
	String name;
	public Eye(String name) {
		this.name=name;
	}
}



原型模式中的复制方法

在上面的原型模式代码中,我们覆写了clone方法,下面我们来进行一个测试:

测试代码一:

package com.prototype;

/**
 * @author zzw922cn
 *
 */
public class Test1 {

	public static void main(String[] args) throws CloneNotSupportedException {
		Dog dog = new Dog(new Eye("红眼睛"));
		dog.setName("狗一");
		
		Dog object1 = (Dog) dog.clone();
		
		object1.eye.name="绿眼睛";
		object1.setName("狗二");
		
		System.out.println(dog.eye.name);
		System.out.println(object1.eye.name);
		
		System.out.println(dog.getName());
		System.out.println(object1.getName());
		
		System.out.println(object1.equals(dog));
		System.out.println(object1==dog);
		System.out.println(object1.getClass().equals(dog.getClass()));
	}
}



在上面的代码中可以看到,object1是dog的克隆对象,当我们克隆完成以后,再对object1进行调用相关设置,改变其Eye类型的变量以及String类型的变量,会发生什么呢?dog是否会发生变化呢?并且object1与dog是否一样呢(equals和==)?它们是否属于同样的类呢?最后一个问题是毋庸置疑的,肯定是同一个类。

运行结果

绿眼睛
绿眼睛
狗一
狗二
false
false
true



从运行结果中可以看到,在object1修改了eye对象以后,dog的eye对象的name也自动由红眼睛变为绿眼睛,但是object1修改了String类型的name对象后,dog却保持原有的name对象。这二者有什么联系或区别吗?联系是String和Eye都是非基本数据类型,Java的八大基本数据类型有char,byte,int,short,long,float,double,boolean。区别是既然同属非基本数据类型,但是一个跟随克隆对象变化而变化,另外一个却保持不变,这是很奇怪的。因为它是String类型,String是一个例外。因此,总结一下clone方法,我们得知克隆对象的基本数据类型字段是原有对象字段的复制,但是非基本类型(String除外)并没有复制,而是对原有对象的非基本类型的一个引用罢了,这种情况正如博文一开始中的newObject与oldObject两者的关系。而String类型则是一个例外,它是对原有对象的一个复制,并非指向原有的String对象的地址。

这种clone一般称为浅复制,即并没有完全复制!

测试代码二

下面来进行一次深复制,即完全复制。我使用流的方式来进行深度复制,即完全拷贝。

深复制

package com.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 Dog implements Cloneable,Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2050795770781171788L;
	
	private String name;
	
	Eye eye;
	
	public Dog(Eye eye) {
		this.eye=eye;
	}

	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name=name;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Dog dog;
		dog=(Dog) super.clone();
		return dog;
	}
	
	/* 深复制 */  
    public Object 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 ois.readObject();  
    } 
    
}
class Eye implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2723012171722328322L;
	String name;
	public Eye(String name) {
		this.name=name;
	}
}



接着写一个类似的测试代码

package com.prototype;

import java.io.IOException;

public class Test2 {
/**
 * equal强调内容是否相同
 * =强调地址是否相同
 * @param args
 * @throws CloneNotSupportedException
 * @throws IOException 
 * @throws ClassNotFoundException 
 */
	public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
		Dog dog = new Dog(new Eye("红眼睛"));
		dog.setName("狗一");
		
		System.out.println("-----------------深复制--------------");
		Dog object2 = (Dog) dog.deepClone();
		object2.eye.name="绿眼睛";
		object2.setName("狗二");
		
		System.out.println(dog.eye.name);
		System.out.println(object2.eye.name);
		
		System.out.println(dog.getName());
		System.out.println(object2.getName());
		
		System.out.println(object2.equals(dog));
		System.out.println(object2==dog);
		System.out.println(object2.getClass().equals(dog.getClass()));
		
		
	}
}



运行测试结果:
-----------------深复制--------------
红眼睛
绿眼睛
狗一
狗二
false
false
true



我们看到深度复制,二者便“分道扬镳”了,除了复制之初两者是一样的之外,后续的任何变化都不会对彼此产生任何影响了。这就是深复制。

equals与==的区别

前面我们看到克隆对象与原始对象的equals和==都返回false,无论是浅复制还是深复制。那么equals和==到底是什么关系呢?

对于基本数据类型,==比较的是它们的值。而对于非基本类型的对象,==比较是它们在内存中的地址,如之前的oldObject与newObject二者的地址相同;而equals方法,如果它没有被子类覆写,它最原始的也是比较对象在内存中的地址,如果被子类覆写了,就不好说了。例如在String类型中,equals方法比较的是字符串的“表面值”,它并不是比较对象在内存中的地址,而==比较的是两个字符串在内存中的地址是否一样。例如String str1="Java",String str2=new String("Java");那么二者的关系是str1!=str2,str1.equals(str2)=true。原因是str1存在于字符串常量池中,str2存在于Java堆中,二者地址当然不同;但是二者的表面值是一样的,都是Java,因此equals方法返回true。

© 著作权归作者所有

断桥残雪断桥残雪
粉丝 53
博文 139
码字总数 94909
作品 0
广州
程序员
私信 提问
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
304
0
java中23种设计模式(上)

参考网址:http://blog.csdn.net/zhangerqing 资源:http://download.csdn.net/detail/zhangerqing/4835830 设计模式(Design Patterns) 设计模式(Design pattern)是一套被反复使用、多数...

青涩的梦
2018/06/26
0
0
Spring中Bean的作用域有哪些?

在Spring的早期版本中,仅有两个作用域:singleton和prototype,前者表示Bean以单例的方式存在;后者表示每次从容器中调用Bean时,都会返回一个新的实例,prototype通常翻译为原型。 补充:设...

唐怀瑟
2018/07/25
0
0
Java之美[从菜鸟到高手演变]之设计模式

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代...

只想一个人静一静
2014/02/25
240
2
java 23种设计模式 深入理解

以下是学习过程中查询的资料,别人总结的资料,比较容易理解(站在各位巨人的肩膀上,望博主勿究) 创建型 抽象工厂模式 http://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html ...

wc_飞豆
2018/03/16
202
0

没有更多内容

加载失败,请刷新页面

加载更多

什么样的人要学点python编程?请你对号入座

什么样的人需要学点python编程? 时代越来越不一样了,编程这种专业程序员的工作,已经开始应用于各种其他日常工作中,就以前象征着互联网的电脑,现在早已进入普通人家。 那么什么样的人需要...

这人就爱编程
22分钟前
3
0
哪吒之魔童降世的背景音乐怎么提取 视频中提取音频的方法

随着国漫的不断崛起一大批优质的动漫正向我们袭来,从大鱼海棠到大圣归来再到我们现在的哪吒让我们看到国漫质的飞跃,也让我们对国漫充满信心,前段时间哪吒之魔童降世以国产第二的票房下线到...

cenfeng123
25分钟前
3
0
springcloud 整合 springboot-admin 监控中心

Admin监控应用 Spring Boot提供的监控接口,例如:/health、/info等等,实际上除了之前提到的信息,还有其他信息业需要监控:当前处于活跃状态的会话数量、当前应用的并发数、延迟以及其他度...

java框架开发者
28分钟前
2
0
GMAT阅读提分要看课外读物,名师点评正确使用方法

阅读提分需要从积累阅读量开始,这种积累不能只靠做练习,还需要大量阅读课外读物。而最适合GRE考生的课外读物之一就是原版杂志。但看课外读物提分也要讲究方法。下面小编就为大家详解看课外...

bole6
30分钟前
1
0
第六次读Kingfisher网络图片缓存库的思考与感受(稍微有点起色)

这节我们来优化一下之前的 硬盘存储,看看kingfiisher哪里做得好,我们稍微来学习一下。 从硬盘里检索图片模仿改进: open func retrieveImageInDiskCache(forKey key: String, options:...

T型人才追梦者
30分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部