目錄
概念
原型模式是一種創建型設計模式,它用一個已經創建的實例作爲原型,通過複製該原型對象來創建一個和原型相同或相似的新對象。原型模式(Prototype Pattern)的簡單程度僅次於單例模式和迭代器模式。正是由於簡單,使用的場景才非常地多。
原型模式是一種“另類”的創建型模式,創建克隆對象的工廠就是原型類自身,工廠方法由克隆方法來實現。
Prototype模式允許一個對象再創建另外一個可定製的對象,根本無需知道任何如何創建的細節,工作原理是:通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝它們自己來實施創建。
應用場景
1、對象之間相同或相似,即只是個別的幾個屬性不同的時候。
2、對象的創建過程比較麻煩,但複製比較簡單的時候。
3、創建新對象成本比較大。
4、一個對象有多個修改人。
5、對對象的狀態進行記錄。
分類
原型模式的核心就是拷貝對象,那麼我們能拷貝一個對象實例的什麼內容呢?這就要區分深拷貝和淺拷貝之分了。
淺拷貝
我們只拷貝對象中的基本數據類型(8種),對於數組、容器、引用對象等都不會拷貝。需要注意的是,淺克隆方式創建一個新對象,新對象的屬性和原來對象完全相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的內存地址。
深拷貝
不僅能拷貝基本數據類型,還能拷貝那些數組、容器、引用對象等。需要注意的是,深克隆方式創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。
優缺點
優點
1、性能優良
原型模式是在內存二進制流的拷貝,要比直接new一個對象性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。另外,當創建新的對象實例較爲複雜時,使用原型模式可以簡化對象的創建過程,通過複製一個已有實例可以提高新實例的創建效率。
2、逃避構造函數的約束
這既是它的優點也是缺點,直接在內存中拷貝,構造函數是不會執行的。 優點就是減少了約束,缺點也是減少了約束,需要大家在實際應用時考慮。
3、擴展性好
由於在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進行編程,而將具體原型類寫在配置文件中,增加或減少產品類對原有系統都沒有任何影響。
4、提供了簡化的創建結構方式
原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的工廠等級結構,而原型模式就不需要這樣,原型模式中產品的複製是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。
缺點
1、違反開閉原則
需要爲每一個類配置一個克隆方法,而且該克隆方法位於類的內部,當對已有類進行改造的時候,需要修改代碼,違反了開閉原則。
2、多重嵌套引用時,實現麻煩
在實現深克隆時需要編寫較爲複雜的代碼,而且當對象之間存在多重嵌套引用時,爲了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來會比較麻煩。
3、無法和單例模式組合使用
由於使用原型模式複製對象時不會調用類的構造方法,所以原型模式無法和單例模式組合使用。
角色構成
Prototype(抽象原型類)
聲明克隆方法的接口,是所有具體原型類的公共父類,它可是抽象類也可以是接口,甚至可以是具體實現類。
ConcretePrototype(具體原型類)
它實現抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。
Client(客戶端訪問類)
在客戶類中,讓一個原型對象克隆自身從而創建一個新的對象。
靈魂拷問
爲什麼不用new直接新建對象,而要用原型模式?
用new新建對象不能獲取當前對象運行時的狀態,其次就算new了新對象,在將當前對象的值複製給新對象,效率也不如原型模式高。
爲什麼不直接使用拷貝構造函數,而要使用原型模式?
原型模式與拷貝構造函數是不同的概念,拷貝構造函數涉及的類是已知的,原型模式涉及的類可以是未知的(基類的拷貝構造函數只能複製得到基類的對象)。
原型模式使用小結
原型模式大都是用來創建重複的同類型但屬性稍微不同的對象,但缺點就是要考慮深克隆的場景,如果一個類很複雜,裏面有許多需要深度克隆的變量引用時,那麼對於這個類而言,進行克隆的時候要妥善處理好變量中的深度克隆。
代碼實現
淺克隆
ShallowClassroom.java
package pattern.prototype.shallow;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class ShallowClassroom {
private String className;
public ShallowClassroom() {
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"ClassName\":\"").append(className).append('\"');
sb.append('}').append(super.toString());
return sb.toString();
}
}
ShallowStudent.java
package pattern.prototype.shallow;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class ShallowStudent implements Cloneable {
private String name;
private Integer age;
private ShallowClassroom classroom;
public ShallowStudent() {
System.out.println("ShallowStudent類的無參構造器...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public ShallowClassroom getClassroom() {
return classroom;
}
public void setClassroom(ShallowClassroom classroom) {
this.classroom = classroom;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"Name\":\"").append(name).append('\"');
sb.append(",\"Age\":").append(age);
sb.append(",\"Classroom\":").append(classroom);
sb.append('}').append(" ").append(super.toString());
return sb.toString();
}
}
ShallowCloneClient
package pattern.prototype.shallow;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class ShallowCloneClient {
/**
* 案例:一個班級有多個學生,每個學生只對應一個班級
*/
public static void main(String[] args) throws CloneNotSupportedException {
ShallowClassroom classroom = new ShallowClassroom();
classroom.setClassName("高三<7>班");
ShallowStudent student1 = new ShallowStudent();
student1.setName("Jone");
student1.setAge(20);
student1.setClassroom(classroom);
ShallowStudent student2 = (ShallowStudent) student1.clone();
ShallowStudent student3 = (ShallowStudent) student1.clone();
// 先觀察clone出來的對象地址
System.out.println("各個對象的地址");
System.out.println(student1.toString());
System.out.println(student2.toString());
System.out.println(student3.toString());
System.out.println("修改student2和student3的屬性");
student2.setName("Marry");
student2.setAge(18);
student3.setName("Stan");
student3.setAge(19);
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
//淺克隆:對一處改變,其它擁有相同引用的對象也都改變,所以上面所有的課程都變成了一樣。
System.out.println("Marry被調到高三<1>班");
student2.getClassroom().setClassName("高三<1>班");
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
}
}
淺克隆控制檯輸出
深克隆
DeepClassroom.java
package pattern.prototype.deep;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class DeepClassroom implements Cloneable {
private String className;
public DeepClassroom() {
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"ClassName\":\"").append(className).append('\"');
sb.append('}').append(super.toString());
return sb.toString();
}
}
DeepStudent.java
package pattern.prototype.deep;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class DeepStudent implements Cloneable {
private String name;
private Integer age;
private DeepClassroom classroom;
public DeepStudent() {
System.out.println("DeepStudent類的無參構造器...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public DeepClassroom getClassroom() {
return classroom;
}
public void setClassroom(DeepClassroom classroom) {
this.classroom = classroom;
}
@Override
protected Object clone() throws CloneNotSupportedException {
DeepStudent student = (DeepStudent) super.clone();
student.setClassroom((DeepClassroom) student.classroom.clone());
return student;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"Name\":\"").append(name).append('\"');
sb.append(",\"Age\":").append(age);
sb.append(",\"Classroom\":").append(classroom);
sb.append('}').append(" ").append(super.toString());
return sb.toString();
}
}
DeepCloneClient.java
package pattern.prototype.deep;
/**
* 〈功能詳細描述〉
*
* @author 劉斌
* @date 2020/3/7
* @see [相關類/方法](可選)
* @since [產品/模塊版本] (可選)
*/
public class DeepCloneClient {
public static void main(String[] args) throws CloneNotSupportedException {
DeepStudent student1 = new DeepStudent();
DeepClassroom classroom = new DeepClassroom();
classroom.setClassName("高三<7>班");
student1.setName("Jone");
student1.setAge(20);
student1.setClassroom(classroom);
DeepStudent student2 = (DeepStudent) student1.clone();
DeepStudent student3 = (DeepStudent) student1.clone();
// 先觀察clone出來的對象地址
System.out.println("各個對象的地址");
System.out.println(student1.toString());
System.out.println(student2.toString());
System.out.println(student3.toString());
System.out.println("修改student2和student3的屬性");
student2.setName("Marry");
student2.setAge(18);
student3.setName("Stan");
student3.setAge(19);
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
System.out.println("Marry被調到高三<1>班");
student2.getClassroom().setClassName("高三<1>班");
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
}
}
深克隆控制檯輸出
由深克隆控制檯輸出結果,可以看出,使用強克隆可以爲引用對象開闢一塊內存地址而不是像淺克隆那樣指向同一個引用對象。