java教程:關於Java設計模式關於原型模式(Prototype) IT信息技術http://www.52ij.com/jishu/
首先需要弄清楚什麼叫原型模式,或者說為什麼要有原型模式,運用它會給我們帶來什麼或能解決什麼問題?原型模式(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