JAVA設計模式之 原型模式【Prototype Pattern】

小呂-ICE發表於2014-11-06

一、概述: 

    使用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。簡單的說就是物件的拷貝生成新的物件(物件的克隆),原型模式是一種物件建立型模式


二、使用場景:

    建立新的物件可以通過對已有物件進行復制來獲得,如果是相似物件,則只需對其成員變數稍作修改。


三、UML結構圖:



四、參與者

(1)    Prototype(抽象原型類):它是宣告克隆方法的介面,是所有具體原型類的公共父類,可以是抽象類也可以是介面,甚至還可以是具體實現類。

(2)    ConcretePrototype(具體原型類):它實現在抽象原型類中宣告的克隆方法,在克隆方法中返回自己的一個克隆物件。

(3)    Client(客戶類):讓一個原型物件克隆自身從而建立一個全新的物件。


五、用例學習:

1、抽象原型類:Prototype.java

/**
 * 抽象原型類
 * @author  lvzb.software@qq.com
 *
 */
public abstract class Prototype {
	
	/**
	 * 提供抽象克隆方法
	 */
	public abstract Prototype clone();

}
2、具體原型類:ConcretePrototypeA.java

/**
 * 具體原型類A
 * @author  lvzb.software@qq.com
 *
 */
public class ConcretePrototypeA extends Prototype {

	/**
	 * 淺克隆
	 */
	@Override
	public Prototype clone() {
		Prototype prototype = new ConcretePrototypeA();
		return prototype;
	}

}
3、客戶端測試類:Client.java

public class Client {

	public static void main(String[] args) {
		Prototype prototypeA = new ConcretePrototypeA();
		Prototype prototypeB = prototypeA.clone();
		
		System.out.println(prototypeB.equals(prototypeA));   // return false
		System.out.println(prototypeB == prototypeA);   // return false
		System.out.println(prototypeB.getClass() == prototypeA.getClass());  // return true

	}

}
這裡我們可以看到 prototypeA物件克隆了一個物件prototypeB,但是prototypeA  != prototypeB, 說明prototypeB是一個全新的Prototype物件。


    注意:原型模式通過克隆方法所建立的物件是全新的物件,它們在記憶體中擁有新的地址,通常對克隆所產生的物件進行修改對原型物件不會造成任何影響,每一個克隆物件都是相互獨立的。


六、擴充套件:

關於淺克隆與深克隆的簡單介紹:

(1) 在Java語言中,資料型別分為值型別(基本資料型別)引用型別,值型別包括int、double、byte、boolean、char等簡單資料型別,引用型別包括類、介面、陣列等複雜型別。如下Person物件:

public class Person {
// 姓名
private String name;
// 年齡
private int age;
// 他的父親
private Father father;
}

name、age 為基本資料型別,father就為引用型別。

淺克隆和深克隆的主要區別在於是否支援引用型別的成員變數的複製


(2)淺克隆:

在Java語言中,通過覆蓋Object類的clone()方法就是實現淺克隆,在淺克隆中,當物件被複制時只複製它本身和其中包含的值型別的成員變數,而引用型別的成員物件並沒有複製,也就是說原型物件只是將引用物件的地址複製一份給克隆物件,克隆物件和原型物件的引用型別成員變數還是指向相同的記憶體地址。

注意:能夠實現克隆的Java類必須實現一個標識介面Cloneable,表示這個Java類支援被複制。如果一個類沒有實現這個介面但是呼叫了clone()方法,Java編譯器將丟擲一個CloneNotSupportedException異常。


用程式碼說話:

1、引用物件:Father.java

public class Father{
	// 姓名
	private String name;
	// 年齡
	private int age;
	
	public Father(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}
2、克隆原型類: Person.java

public class Person implements Cloneable{
	// 姓名
	private String name;
	// 年齡
	private int age;
	// 他的父親
	private Father father;

	/**
	 * 重寫 Object物件的clone方法實現Person物件的克隆
	 */
	public Person clone(){
		Object obj = null;
		try {
			obj = super.clone();
			return (Person)obj;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Father getFather() {
		return father;
	}

	public void setFather(Father father) {
		this.father = father;
	}
}
3、測試類:CloneClient.java

public class CloneClient {

	public static void main(String[] args) {
		Father father = new Father("老子", 50);
		Person son = new Person();
		son.setName("兒子");
		son.setAge(24);
		son.setFather(father);
		
		// 淺克隆出一個兄弟Person物件
		Person brother = son.clone();
		System.out.println(brother == son);  // return false
		System.out.println(brother.getFather() == son.getFather());  // return true
	}

}
以上 我們可以分析看到son 淺克隆出一個"兄弟"物件 brother,但是他們的引用物件"父親"都是同一個物件,所有事實證明淺克隆沒有對引用型別物件進行復制


(3)深克隆:

    在深克隆中,無論原型物件的成員變數是值型別還是引用型別,都將複製一份給克隆物件,簡單來說,在深克隆中,除了物件本身被複制外,物件所包含的所有成員變數也將複製。


    那麼如何實現深克隆呢?

    在Java語言中,如果需要實現深克隆,可以通過序列化(Serialization)等方式來實現。序列化就是將物件寫到流的過程,寫到流中的物件是原有物件的一個拷貝,而原物件仍然存在於記憶體中。通過序列化實現的拷貝不僅可以複製物件本身,而且可以複製其引用的成員物件,因此通過序列化將物件寫到一個流中,再從流裡將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的物件其類必須實現Serializable介面,否則無法實現序列化操作


用程式碼說話:

1、引用類:Father.java

import java.io.Serializable;

public class Father implements Serializable{
	// 姓名
	private String name;
	// 年齡
	private int age;
	
	public Father(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}
2、克隆原型類: Person.java

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;

public class Person implements Serializable{
	// 姓名
	private String name;
	// 年齡
	private int age;
	// 他的父親
	private Father father;
	
	/**
	 * 深克隆
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws OptionalDataException
	 */
	public Person deepClone() throws IOException, ClassNotFoundException, OptionalDataException
	{
	       //將物件寫入流中
	       ByteArrayOutputStream bao=new  ByteArrayOutputStream();
	       ObjectOutputStream oos=new  ObjectOutputStream(bao);
	       oos.writeObject(this);
	      
	       //將物件從流中取出
	       ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
	       ObjectInputStream ois=new  ObjectInputStream(bis);
	       return  (Person) ois.readObject();
	}
	
	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Father getFather() {
		return father;
	}

	public void setFather(Father father) {
		this.father = father;
	}
}
3、深克隆測試類:DeepCloneClient.java

public class DeepCloneClient {

	public static void main(String[] args) {
		Father father = new Father("老子", 50);
		Person son = new Person();
		son.setName("兒子");
		son.setAge(24);
		son.setFather(father);
		
		try {
			Person brother = son.deepClone();
			System.out.println(brother == son);  // false
			System.out.println(brother.getFather() == son.getFather());  // false
			
		} catch (OptionalDataException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

}
以上我們可以分析看到通過深克隆出來的"兄弟"物件brother 和 son 不僅不等、就連他們的引用型別Father也不等啦。所有證明:通過深克隆 克隆出了一個完全獨立的全新的物件。


相關文章