文章目錄
- 前言
- 一.原型模式(用得最少)
- 二.原型模式适用場景
- 二.原型模式角色
-
- 1.角色分類
- 2.标準的原型模式實作方式
-
- 2.1.抽象原型角色(Abstract Prototype)
- 2.2.具體原型角色(Concrete Prototype)
- 2.3.測試類
- 三.Java中原型模式的實作方式
-
- 1.淺克隆
-
- 1.1.代碼實作
- 1.1.原型模式和直接new對象方式的比較
- 2.深克隆
-
- 2.1.基于淺克隆嵌套
- 2.2.基于序列化反序列化
- 四.總結
-
- 1.原型模式的優缺點
- 2.原型模式在開發中的應用場景
前言
建議先看這篇文章【設計模式】軟體設計7大原則以及23種設計模式初識(一)
一.原型模式(用得最少)
我們知道通過new關鍵字建立的對象是非常繁瑣的(類加載判斷,記憶體配置設定,初始化等),當直接建立對象的代價比較大時,我們可以采用原型模式(Prototype Pattern)。
通過克隆已存在的對象,減少重新建立對象的時間開銷
- 原型模式也稱為
, 即某個對象為原型克隆出來一個一模一樣的對象,該對象的屬性和原型對象一模一樣。而且對于原型對象沒有任何影響。原型模式的克隆方式有2種:"克隆模式"
淺克隆和深度克隆
- 這裡我簡單的說明一下什麼是淺克隆和深克隆
淺克隆和深克隆的概念:
- 淺克隆:将一個對象複制後,基本資料類型的變量都會重新建立,而引用類型,指向的還是原對象所指向的。
- 實作Cloneable 接口,重寫clone()方法
- 深克隆:将一個對象複制後,不論是基本資料類型還有引用類型,都是重新建立的。簡單來說,就是深克隆進行了完全徹底的複制,而淺克隆不徹底。
- `實作Serializable接口,将對象進行序列化成二進制流,然後再将二進制流進行反序列化成Java對象
- 淺克隆和深克隆的實作可以看這片文章【Java基礎】對象深克隆和淺克隆的原理及實作
- 這裡我簡單的說明一下什麼是淺克隆和深克隆
二.原型模式适用場景
- 在需要一個類的
的時候,使用原型模式是最佳選擇,因為原型模式是大量對象
,要比直接new這個對象性能要好很多。在記憶體中對這個對象進行拷貝
- 如果一個對象的初始化需要很多
,那麼可以使用原型模式,這樣建立新的對象的時候,可以避免其他對象的資料準備和計算,其他對象的資料準備或其他資源的繁瑣計算
。直接得到目前對象的副本
- 當需要
,也可以使用原型模式拷貝出現有對象的副本後再進行加工處理。保留一個複雜對象的絕大部分資訊,少量字段進行個性化設定的時候
二.原型模式角色
1.角色分類
- 抽象原型角色(Abstract Prototype):這是一個抽象角色,通常由一個
,此抽象角色規定了Java接口或java抽象類實作
(如果要提供深拷貝,則必須具有實作clone的規定)。所有的具體原型類所需的接口
- 具體原型角色(Concrete Prototype):即被複制的對象,此角色需要
。實作抽象的原型角色所要求的接口
- 客戶角色(Client):使用原型對象的客戶程式
2.标準的原型模式實作方式
-
:實作原型接口,基于現有的對象克隆一個新的對象出來,由對象的内部提供克隆的方法,通過clone方法傳回一個對象的副本。主要思想
2.1.抽象原型角色(Abstract Prototype)
原型接口
public interface IPrototype {
IPrototype clone();
}
2.2.具體原型角色(Concrete Prototype)
PrototypeImplA實作了接口IPrototype,并且實作了clone方法,傳回了一個新的對象
public class PrototypeImplA implements IPrototype{
private String name;
private int age;
private List<String> phoneList;
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 List<String> getPhoneList() {return phoneList;}
public void setPhoneList(List<String> phoneList) {this.phoneList = phoneList; }
//實作原型接口clone()方法
@Override
public IPrototype clone() {
PrototypeImplA prototypeImplA = new PrototypeImplA();
prototypeImplA.setAge(this.age);
prototypeImplA.setName(this.name);
prototypeImplA.setPhoneList(this.phoneList);
return prototypeImplA;
}
}
2.3.測試類
public class ProtoTypeTest {
public static void main(String[] args) throws Exception {
PrototypeImplA prototypeImplA = new PrototypeImplA();
prototypeImplA.setAge(18);
prototypeImplA.setName("張三");
List<String> phoneList = new ArrayList<>();
phoneList.add("88888888");
phoneList.add("77777777");
prototypeImplA.setPhoneList(phoneList);
PrototypeImplA cloneProtoTypeA = (PrototypeImplA) prototypeImplA.clone();
System.out.println(prototypeImplA+"------"+prototypeImplA.getPhoneList().hashCode());
System.out.println(cloneProtoTypeA+"------"+cloneProtoTypeA.getPhoneList().hashCode());
}
}
生成了2個不同的PrototypeImplA,但因為這種克隆方式是
淺克隆
,
對象中如果有引用對象那麼被克隆後的對象依然會指向原對象
,如果需要複制兩個獨立的對象,則需要使用
深克隆
,後面示例中我們會對比一下2種克隆方式
對象的HashCode相同,說明是指向的是同一個記憶體位址
三.Java中原型模式的實作方式
1.淺克隆
1.1.代碼實作
/**
* 原型類:被克隆的類型
* Object有clone()方法,但不能直接使用,需要實作Cloneable,重寫裡面的clone()方法才能使用
*/
public class Student implements Cloneable {
public Student() {
System.out.println("執行了構造方法Student()");
}
/**
* 傳回類型是Object
*/
@Override
protected Object clone() throws CloneNotSupportedException {
//調用Object的clone()
return super.clone();
}
}
測試
public static void main(String[] args) {
Student student = new Student();
try {
Student cloneStudent= (Student) student.clone(); //通過克隆來建立執行個體
System.out.println(student);
System.out.println(cloneStudent);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
測試結果
1.1.原型模式和直接new對象方式的比較
測試通過原型模式建立100萬對象 和通過new建立100萬對象的性能
public static void main(String[] args) throws CloneNotSupportedException {
int size = 1_000_000;
long start1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
Student t = new Student();
}
long end1 = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
Student t = new Student();
for (int i = 0; i < size; i++) {
Student temp = (Student) t.clone();
}
long end2 = System.currentTimeMillis();
System.out.println("new的方式建立耗時:" + (end1 - start1));
System.out.println("clone的方式建立耗時:" + (end2 - start2));
}
執行結果:
結論:
- 通過clone的方式在擷取大量對象的時候性能開銷基本沒有什麼影響,而new的方式随着執行個體的對象越來越多,性能會急劇下降,是以原型模式是一種比較重要的擷取執行個體的方式。
優點:
- 使用clone方法建立的新對象的
,也就是說會繞過任何構造函數(有參和無參),因為clone方法的原理是從堆記憶體中以二進制流的方式進行拷貝,直接配置設定一塊新記憶體。構造函數是不會被執行的
- 一句話:
clone()直接複制二進制流,不調用構造器建立執行個體
缺點:
- 被拷貝對象的所有基本類型變量都含有與原對象相同的值,而且
。即引用類型的變量仍然是指向原對象位址
。淺拷貝隻拷貝目前基本類型屬性值,對引用的屬性值不做拷貝
2.深克隆
2.1.基于淺克隆嵌套
public class Student implements Cloneable {
//引用型成員變量。注意:Teacher類也要實作Cloneable接口,并重寫clone()方法
private Teacher teacher;
public Student() {
System.out.println("執行了構造方法Student()");
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone(); //克隆整個對象
student.teacher = (Teacher) teacher.clone();
return student;
}
}
class Teacher implements Cloneable {
public Teacher() {
System.out.println("執行了構造方法Teacher()");
}
@Override
protected Object clone() throws CloneNotSupportedException {
//克隆整個對象
Teacher teacher = (Teacher) super.clone();
return teacher;
}
}
測試
public static void main(String[] args) {
Student student = new Student();
student.teacher= new Teacher();
try {
Student cloneStudent= (Student) student.clone(); //通過克隆來建立執行個體
System.out.println(student+"------"+student.teacher);
System.out.println(cloneStudent+"------"+cloneStudent.teacher);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
執行結果
2.2.基于序列化反序列化
- 序列化 把對象轉換為位元組流。
- 反序列化 把位元組流恢複為對象。
/**
* 序列化實作深克隆
*/
public class Student implements Cloneable, Serializable {
//引用型成員變量。注意:Teacher類也要實作Serializable
private Teacher teacher;
public Student() {
System.out.println("執行了構造方法Student()");
}
public static void main(String[] args) throws CloneNotSupportedException {
//原對象
Student student = new Student();
student.teacher = new Teacher();
//克隆對象
Student cloneStudent = (Student) student.clone();
System.out.println(student+"-----"+student.teacher);
System.out.println(cloneStudent+"-----"+cloneStudent.teacher);
}
//序列化實作深克隆
private Object deepClone() {
try {
//----------序列化
//位元組數組輸出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//對象輸出流
ObjectOutputStream oos = new ObjectOutputStream(bos);
//将對象寫入對象輸出流
oos.writeObject(this);
//輸出成位元組數組
byte[] bytes = bos.toByteArray();
//----------反序列化
//讀取對象位元組數組
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
//讀取位元組數組流
ObjectInputStream ois = new ObjectInputStream(bis);
//從位元組流中生成對象
Student cloneStudent = (Student) ois.readObject();
return cloneStudent;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
return deepClone();
}
}
class Teacher implements Serializable {
public Teacher() {
System.out.println("執行了構造方法Teacher()");
}
}
測試
public static void main(String[] args) throws CloneNotSupportedException {
//原對象
Student student = new Student();
student.teacher = new Teacher();
//克隆對象
Student cloneStudent = (Student) student.clone();
System.out.println(student+"-----"+student.teacher);
System.out.println(cloneStudent+"-----"+cloneStudent.teacher);
}
四.總結
1.原型模式的優缺點
- 優點:減少了重新建立執行個體、反複的相同指派操作的性能開銷;
- 原型模式是在記憶體二進制流的拷貝, 要比直接new一個對象性能好很多, 特别是要在一個循環體内産生大量的對象時, 原型模式可以更
- 缺點:克隆
會非常麻煩,需要編寫較為複雜的代碼包含循環引用的複雜對象
無論是深克隆還是、淺克隆在沒有經過處理時,都會破壞單例模式,重新生成一個新的執行個體
2.原型模式在開發中的應用場景
- 原型模式很少單獨出現,
一般是和工廠方法模式一起出現,通過clone的方法建立一個對象,然後由工廠方法提供給調用者
。
• Spring中bean的建立實際就是兩種:單例模式和原型模式。(原型模式需要和工廠模式搭配起來)
原型模式了解到這裡我覺得就夠了,各種變着法子說這種代碼或那種代碼是原型模式,沒什麼意義。