浅谈Java设计模式关于原型模式(Prototype)思索建议

2013-02-15itjishu 的分享   加三联MM小编微信好友:sanlian2018

java教程:关于Java设计模式关于原型模式(Prototype) IT信息技术http://www.52ij.com/jishu/


浅谈Java设计模式关于原型模式(Prototype)思索建议 三联


首先需要弄清楚什么叫原型模式,或者说为什么要有原型模式,运用它会给我们带来什么或能解决什么问题?原型模式(Prototype)同抽象工厂模式同属于创建型模式,它主要关注于大量相同或相似对象的创建问题,应用原型模式就是先需要一个原对象,然后通过对原对象进行复制(克隆),来产生一个与原对象相同或相似的新对象。注意这里所说的对象相同不是指复制出来的副本对象与原对象是同一个对象,相反复制出来的副本对象必须和原对象是两个不同的对象,只是两个对象的内容相同。

我们在编程过程中,经常会遇到这种情况,需要把一个对象的值赋值到另一个新对象,而且以后对新对象属性的修改不会影响到原对象,即两个对象是相互独立的,比如:
public clalss A{
private Long id;
private String name;
get set略......
public A(){}
public A(Long id,String name){
this.id = id;

this.name = name;
}
}
A a = new A(1,"a");
A b = null;
如果现在需要把对象a的内容全部复制到对象b中去,怎么办?如果你这样写,b = a;那就大错特错了,因为这样根据没有创建新对象,两个都是指向内存中同一个地址。或许你又会这样,b = new A(); 然后b.setId();
b.setName();这样一个一个的赋值。这样是创建了两个独立对象,但是还是有问题,如果对象属性有N多个怎么办,理论上这种情况是存在的,如果还是那样做,岂不是很累?怎么办?运用原型模式呗。实现原型模式很简单,只要待复制对象实现Cloneable接口,并重写父类Object的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.OptionalDataException;
import java.io.Serializable;

/**
* 汤匙基类
*
* @author Lanxiaowei
* @createTime 2011-10-10 09:04
*/
public abstract class Spoon implements Cloneable,Serializable {
/**
* 名称
*/
private String name;
/**
* 价格
*/
private double price;
/**
* 使用人
*/
private People people;

public People getPeople() {
return people;
}

public void setPeople(People people) {
this.people = people;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public Spoon() {
}

public Spoon(String name, double price) {
this.name = name;
this.price = price;
}

public Spoon(String name, double price,People people) {
this.name = name;
this.price = price;
this.people = people;
}

@Override
/**
* 浅复制
*/
protected Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Spoon is not Cloneable!");
}
return object;
}

/**
* 深度复制(推荐使用序列化的方式)
* @return
*/
protected Object deepClone() {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
Object object = null;
try {
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
object = oi.readObject();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return object;
}
/**
* 因为对于引用类型对象默认都是继承自Object类,
* 而Object类的equals()默认实现就是比较两者的内存地址引用,
* 所以需要重写equals()和hashCode()
* 至于为什么重写equals()还要重写hashCode(),那是sun的推荐做法
* (因为当对象作为散列key时用到key的hashCode,
* 而sun又规定两个对象若equals()为true,则hashCode()返回结果也应该相等)
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
long temp;
temp = Double.doubleToLongBits(price);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Spoon other = (Spoon) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (Double.doubleToLongBits(price) != Double.doubleToLongBits(other.price))
return false;
return true;
}
}

 

package com.prototype;
/**
* 汤匙子类
* @author Lanxiaowei
* @createTime 2011-10-10 09:20
*/
public class SoupSpoon extends Spoon {
public SoupSpoon() {
super();
}

public SoupSpoon(String name,double price){
super(name,price);
}

public SoupSpoon(String name,double price,People people){
super(name,price,people);
}
}

 

 

package com.prototype;

import java.io.Serializable;

/**
* 使用人
*
* @author Lanxiaowei
* @createTime 2011-10-10 11:19
*/
public class People implements Serializable{
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public People(){}

public People(String name){
this.name = name;
}
}

 

package com.prototype;
/**
* 测试类
*
* @author Lanxiaowei
* @createTime 2011-10-10 11:34
*/
public class Test {
public static void main(String[] args) {
/******************************************测试对象浅复制 begin************************************************/
//通过Cloneable接口浅复制一个新对象(注意Cloneable接口对于引用类型对象只能复制内存地址引用,即"浅复制")
Spoon spoon1 = new SoupSpoon("汤匙",20.5);
Spoon spoon2 = (SoupSpoon)spoon1.clone();

System.out.println("spoon1与spoon2相等吗?" + (spoon1.equals(spoon2)));
System.out.println("spoon1与spoon2是同一个对象吗?" + (spoon1 == spoon2));

System.out.println("");

//直接通过new关键字创建一个新对象
Spoon spoon3 = new SoupSpoon("汤匙",20.5);
Spoon spoon4 = new SoupSpoon("汤匙",20.5);

System.out.println("spoon3与spoon4相等吗?" + (spoon3.equals(spoon4)));
System.out.println("spoon3与spoon4是同一个对象吗?" + (spoon3 == spoon4));
/******************************************测试对象浅复制 end**************************************************/

System.out.println("");

/******************************************测试对象深复制 begin************************************************/
//通过deepClone()深复制对象
Spoon spoon5 = new SoupSpoon("汤匙",20.5,new People("zhangsan"));
Spoon spoon6 = (SoupSpoon)spoon5.deepClone();
System.out.println("spoon5与spoon6相等吗?" + (spoon5.equals(spoon6)));
System.out.println("spoon5与spoon6是同一个对象吗?" + (spoon5 == spoon6));
System.out.println("spoon5与spoon6的people是同一个对象吗?" + (spoon5.getPeople() == spoon6.getPeople()));
/******************************************测试对象深复制 end**************************************************/
}
}

其他不想多说,代码里已经表达的很清楚,唯一需要说明的是,Java对象复制分浅复制和深复制,浅复制指的是只复制非引用类型对象,深复制指的是如果类与类之间存在聚合依赖关系,那些被关联的对象也会被复制。每一个需要复制的对象都需要实现Clonable接口(它只是一个标识性接口,没有任何方法)并重写Object的clone()方法,如果一个类A关联类B,类B又关联类C.....理论上这种关系可能存在N层嵌套,如果还是每个类都这样处理,那麻烦就大了,怎么办?这时,我建议还是使用序列化的方式来实现对象深度复制比较好,首先用序列化方式实现代码才几行,非常简洁,再个就是可以用递归方式实现里,至于如何实现,下面会贴相关代码。
下面就要考虑重用性了,于是需要考虑写个工具类,来专门用于解决Java对象复制问题,下面是java对象克隆工具类的具体代码:
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.OptionalDataException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* 克隆工具类
* @author Lanxiaowei
* @createTime 2011-10-10 12:51
*/
public class ClonUtils {
/**
* 无需进行复制的特殊类型数组
*/
static Class[] needlessCloneClasses = new Class[]{String.class,Boolean.class,Character.class,Byte.class,Short.class,
Integer.class,Long.class,Float.class,Double.class,Void.class,Object.class,Class.class
};
/**
* 判断该类型对象是否无需复制
* @param c 指定类型
* @return 如果不需要复制则返回真,否则返回假
*/
private static boolean isNeedlessClone(Class c){
if(c.isPrimitive()){//基本类型
return true;
}
for(Class tmp:needlessCloneClasses){//是否在无需复制类型数组里
if(c.equals(tmp)){
return true;
}
}
return false;
}

/**
* 尝试创建新对象
* @param c 原始对象
* @return 新的对象
* @throws IllegalAccessException
*/
private static Object createObject(Object value) throws IllegalAccessException{
try {
return value.getClass().newInstance();
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
throw e;
}
}

/**
* 复制对象数据
* @param value 原始对象
* @param level 复制深度。小于0为无限深度,即将深入到最基本类型和Object类级别的数据复制;
* 大于0则按照其值复制到指定深度的数据,
* 等于0则直接返回对象本身而不进行任何复制行为。
* @return 返回复制后的对象
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public static Object clone(Object value,int level) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException{
if(null == value){
return null;
}
if(level==0){
return value;
}
Class c = value.getClass();
if(isNeedlessClone(c)){
return value;
}
level--;
if(value instanceof Collection){//复制新的集合
Collection tmp = (Collection)c.newInstance();
for(Object v:(Collection)value){
tmp.add(clone(v,level));//深度复制
}
value = tmp;
}
else if(c.isArray()){//复制新的Array
//首先判断是否为基本数据类型
if(c.equals(int[].class)){
int[] old = (int[])value;
value = (int[])Arrays.copyOf(old, old.length);
}
else if(c.equals(short[].class)){
short[] old = (short[])value;
value = (short[])Arrays.copyOf(old, old.length);
}
else if(c.equals(char[].class)){
char[] old = (char[])value;
value = (char[])Arrays.copyOf(old, old.length);
}
else if(c.equals(float[].class)){
float[] old = (float[])value;
value = (float[])Arrays.copyOf(old, old.length);
}
else if(c.equals(double[].class)){
double[] old = (double[])value;
value = (double[])Arrays.copyOf(old, old.length);
}
else if(c.equals(long[].class)){
long[] old = (long[])value;
value = (long[])Arrays.copyOf(old, old.length);
}
else if(c.equals(boolean[].class)){
boolean[] old = (boolean[])value;
value = (boolean[])Arrays.copyOf(old, old.length);
}
else if(c.equals(byte[].class)){
byte[] old = (byte[])value;
value = (byte[])Arrays.copyOf(old, old.length);
}
else {
Object[] old = (Object[])value;
Object[] tmp = (Object[])Arrays.copyOf(old, old.length, old.getClass());
for(int i = 0;i<old.length;i++){
tmp[i] = clone(old[i],level);
}
value = tmp;
}
}
else if(value instanceof Map){//复制新的MAP
Map tmp = (Map)c.newInstance();
Map org = (Map)value;
for(Object key:org.keySet()){
tmp.put(key, clone(org.get(key),level));//深度复制
}
value = tmp;
}
else {
Object tmp = createObject(value);
if(tmp==null){//无法创建新实例则返回对象本身,没有克隆
return value;
}
Set<Field> fields = new HashSet<Field>();
while(c!=null&&!c.equals(Object.class)){
fields.addAll(Arrays.asList(c.getDeclaredFields()));
c = c.getSuperclass();
}
for(Field field:fields){
if(!Modifier.isFinal(field.getModifiers())){//仅复制非final字段
field.setAccessible(true);
field.set(tmp, clone(field.get(value),level));//深度复制
}
}
value = tmp;
}
return value;
}

/**
* 浅表复制对象
* @param value 原始对象
* @return 复制后的对象,只复制一层
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public static Object clone(Object value) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException{
return clone(value,1);
}

/**
* 深度复制对象(通过Java反射+递归方式实现)
* @param value 原始对象
* @return 复制后的对象
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public static Object deepClone(Object value) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException{
return clone(value,-1);
}

/**
* 深度复制对象(采用序列化方式实现,推荐使用这种方法)
* @param obj 被复制对象
* @return 复制后的副本对象
*/
public static Object deepClone2(Object obj){
if(null == obj){
return null;
}
ByteArrayOutputStream bo = new ByteArrayOutputStream();
Object object = null;
try {
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(obj);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
object = oi.readObject();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return object;
}
}
刚才说了,建议用序列化方式,还有一个重要的原因是,递归方式里用到了JDK util包里Arrays工具类里的copyOf方法,而该方法是JDK 1.6以上版本才有的,再个Arrays.copyOf内部实现其实也是调用System.arraycopy()方法来辅助实现的,查看System.arraycopy()方法的源代码,你会发现它被native关键字修饰,被native修饰即表名该方法是一个本地方法,那何为本地方法?也就是说该方法通过JNI技术调用了当前操作系统的DLL文件,这样你的程序就丧失了跨平台性,综合以上考虑,所以我建议采用序列化方式。

原文:java教程:再谈Java设计模式关于原型模式(Prototype)http://www.52ij.com/jishu/104.html
 

TA发布的帖子

864

收藏

605