「補課」進行時:設計模式(6)——原型模式

1. 前文匯總
2. 找工作
這一天,郭靖大俠因為在桃花島調(diào)戲侍女被黃蓉打出了桃花島,這下可玩大了,從桃花島被趕出來吃啥喝啥啊,得趕緊找份工作,西北風可喝不飽肚子哇~~~
這不,我們的郭大俠就開始寫簡歷,準備向丐幫、全真教、白駝山和段氏家族投一份簡歷,看看能不能先混碗飯吃,等老婆的氣消了再回去。
首先,先定義一個簡歷類:
public?class?Resume?{
????private?String?name;
????private?String?position;
????private?int?salary;
????//?省略?get/set
????@Override
????public?String?toString()?{
????????return?"Resume{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????",?position='"?+?position?+?'\''?+
????????????????",?salary="?+?salary?+
????????????????'}';
????}
}
然后,我們的郭大俠開始了熬夜寫簡歷的生活:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????Resume?resume1?=?new?Resume();
????????resume1.setName("小郭");
????????resume1.setPosition("一代大俠");
????????resume1.setSalary(1000);
????????System.out.println(resume1);
????????Resume?resume2?=?new?Resume();
????????resume2.setName("小郭");
????????resume2.setPosition("一代大俠");
????????resume2.setSalary(1200);
????????System.out.println(resume2);
????????Resume?resume3?=?new?Resume();
????????resume3.setName("小郭");
????????resume3.setPosition("一代大俠");
????????resume3.setSalary(1500);
????????System.out.println(resume3);
????????//?...
}
簡歷這么一份一份的寫太累了,工作都沒找到可能先餓死了,不行,小郭同學需要提高寫簡歷的效率,于是,他去找了一個打印機回來:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//?效率倍增,直接循環(huán)開始寫簡歷
????????for?(int?i?=?0;?i?5;?i++)?{
????????????Resume?resume4?=?new?Resume();
????????????int?salary?=?(int)(1000?+?Math.random()?*?(2000?-?1000?+?1));
????????????resume4.setName("小郭");
????????????resume4.setPosition("一代大俠");
????????????resume4.setSalary(salary);
????????????System.out.println(resume4.toString());
????????}
????}
}
這個時候,感覺效率好像還是有點低,每次只能一張一張打印,浪費時間,于是乎,我們的郭大俠又去搞了一個復印機回來。
可是使用復印機需要我們原本的簡歷支持這個功能,聽過這個功能需要擴展 Cloneable 接口:
public?class?ResumeClone?implements?Cloneable?{
????private?String?name;
????private?String?position;
????private?int?salary;
????//?省略?get/set
????@Override
????protected?ResumeClone?clone(){
????????ResumeClone?resumeClone?=?null;
????????try{
????????????resumeClone?=?(ResumeClone)?super.clone();
????????}catch?(CloneNotSupportedException?e){
????????????e.printStackTrace();
????????}
????????return?resumeClone;
????}
????@Override
????public?String?toString()?{
????????return?"ResumeClone{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????",?position='"?+?position?+?'\''?+
????????????????",?salary="?+?salary?+
????????????????'}';
????}
}
然后我們的復印機就能跑起來了:
public?class?TestClone?{
????public?static?void?main(String[]?args)?{
????????int?num?=?5;
????????ResumeClone?resumeClone?=?new?ResumeClone();
????????while?(num?>?0){
????????????ResumeClone?resume1?=?resumeClone.clone();
????????????int?salary?=?(int)(1000?+?Math.random()?*?(2000?-?1000?+?1));
????????????resume1.setName("小郭");
????????????resume1.setPosition("一代大俠");
????????????resume1.setSalary(salary);
????????????System.out.println(resume1.toString());
????????????num?--;
????????}
????}
}
這里實際上我們只有第一個對象是使用打印機打印出來的,后面的對象都是通過復印機直接復印出來的。
這其實就是設計模式中的原型模式。
3. 原型模式
原型模式(Prototype Pattern)的簡單程度僅次于單例模式和迭代器模式。正是由于簡單,使用的場景才非常地多,其定義如下:
Specify the kinds of objects to create using a prototypical instance,andcreate new objects by copying this prototype.(用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。)

這個絕對是最簡單的設計模式,整個模式的核心就只有一個 clone 方法,通過該方法進行對象的拷貝, Java 提供了一個 Cloneable 接口來標示這個對象是可拷貝的,為什么說是「標示」呢?翻開 JDK 的幫助看看 Cloneable 是一個方法都沒有的,這個接口只是一個標記作用,在 JVM 中具有這個標記的對象才有可能被拷貝。那怎么才能從「有可能被拷貝」轉(zhuǎn)換為「可以被拷貝」呢?方法是覆蓋 clone() 方法。
通用代碼:
public?class?PrototypeClass?implements?Cloneable{
????@Override
????protected?PrototypeClass?clone()?{
????????PrototypeClass?prototypeClass?=?null;
????????try?{
????????????prototypeClass?=?(PrototypeClass)?super.clone();
????????}?catch?(CloneNotSupportedException?e)?{
????????????e.printStackTrace();
????????}
????????return?prototypeClass;
????}
}
優(yōu)點:
- 性能優(yōu)良
原型模式是在內(nèi)存二進制流的拷貝,要比直接 new 一個對象性能好很多,特別是要在一個循環(huán)體內(nèi)產(chǎn)生大量的對象時,原型模式可以更好地體現(xiàn)其優(yōu)點。
- 逃避構(gòu)造函數(shù)的約束
這既是它的優(yōu)點也是缺點,直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會執(zhí)行的。優(yōu)點就是減少了約束,缺點也是減少了約束。
4. 構(gòu)造函數(shù)
先看一個簡單的有關構(gòu)造函數(shù)的示例:
public?class?ConstructorDemo?implements?Cloneable?{
????public?ConstructorDemo()?{
????????System.out.println("我被執(zhí)行了。。。");
????}
????@Override
????protected?ConstructorDemo?clone(){
????????ConstructorDemo?demo?=?null;
????????try?{
????????????demo?=?(ConstructorDemo)?super.clone();
????????}catch?(CloneNotSupportedException?e){
????????????e.printStackTrace();
????????}
????????return?demo;
????}
}
public?class?ConstructorTest?{
????public?static?void?main(String[]?args)?{
????????ConstructorDemo?demo?=?new?ConstructorDemo();
????????ConstructorDemo?demo1?=?demo.clone();
????}
}
執(zhí)行結(jié)果如下:
我被執(zhí)行了。。。
就輸出一次,這里可以證明對象拷貝的時候構(gòu)造函數(shù)是不會執(zhí)行的,原因在于拷貝是直接在堆中進行,這其實也可以理解, new 的時候, JVM 要走一趟類加載流程,這個流程非常麻煩,在類加載流程中會調(diào)用構(gòu)造函數(shù),最后生成的對象會放到堆中,而拷貝就是直接拷貝堆中的現(xiàn)成的二進制對象,然后重新一個分配內(nèi)存塊。
5. 淺拷貝和深拷貝
先看一個淺拷貝的案例:
public?class?ShallowCopy?implements?Cloneable?{
????private?ArrayList?array?=?new?ArrayList<>?();
????@Override
????public?ShallowCopy?clone()?{
????????ShallowCopy?copy?=?null;
????????try?{
????????????copy?=?(ShallowCopy)?super.clone();
????????}?catch?(CloneNotSupportedException?e)?{
????????????e.printStackTrace();
????????}
????????return?copy;
????}
????public?void?setValue(String?value)?{
????????this.array.add(value);
????}
????public?ArrayList?getValue()? {
????????return?this.array;
????}
}
public?class?ShallowCopyTest?{
????public?static?void?main(String[]?args)?{
????????ShallowCopy?copy?=?new?ShallowCopy();
????????copy.setValue("123");
????????ShallowCopy?copy1?=?copy.clone();
????????copy1.setValue("456");
????????System.out.println(copy.getValue());
????}
}
執(zhí)行的結(jié)果是:
[123,?456]
這種情況就是淺拷貝, Java 只拷貝你指定的對象,至于你指定的對象里面的別的對象,它不拷貝,還是把引用給你,共享變量,這是一種非常不安全的方式,需要特別注意。
內(nèi)部的數(shù)組和引用對象不會拷貝,其他的原始基本類型和 String 類型會被拷貝。
那么這種情況如何進行一個深拷貝呢?只需要修改一下剛才 clone 的方法:
//?深拷貝
@Override
public?ShallowCopy?clone()?{
????ShallowCopy?copy?=?null;
????try?{
????????copy?=?(ShallowCopy)?super.clone();
????????this.array?=?(ArrayList)?this.array.clone();
????}?catch?(CloneNotSupportedException?e)?{
????????e.printStackTrace();
????}
????return?copy;
}
還是剛才的測試類,這次的運行結(jié)果是:
[123]

