「補課」進行時:設計模式(6)——郭靖大俠帶你學原型模式
1. 前文匯總
2. 找工作
這一天,郭靖大俠因為在桃花島調戲侍女被黃蓉打出了桃花島,這下可玩大了,從桃花島被趕出來吃啥喝啥啊,得趕緊找份工作,西北風可喝不飽肚子哇~~~
這不,我們的郭大俠就開始寫簡歷,準備向丐幫、全真教、白駝山和段氏家族投一份簡歷,看看能不能先混碗飯吃,等老婆的氣消了再回去。
首先,先定義一個簡歷類:
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) {
// 效率倍增,直接循環開始寫簡歷
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.(用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。)
這個絕對是最簡單的設計模式,整個模式的核心就只有一個 clone 方法,通過該方法進行對象的拷貝, Java 提供了一個 Cloneable 介面來標示這個對象是可拷貝的,為什麼說是「標示」呢?翻開 JDK 的幫助看看 Cloneable 是一個方法都沒有的,這個介面只是一個標記作用,在 JVM 中具有這個標記的對象才有可能被拷貝。那怎麼才能從「有可能被拷貝」轉換為「可以被拷貝」呢?方法是覆蓋 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;
}
}
優點:
- 性能優良
原型模式是在記憶體二進位流的拷貝,要比直接 new 一個對象性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。
- 逃避構造函數的約束
這既是它的優點也是缺點,直接在記憶體中拷貝,構造函數是不會執行的。優點就是減少了約束,缺點也是減少了約束。
4. 構造函數
先看一個簡單的有關構造函數的示例:
public class ConstructorDemo implements Cloneable {
public ConstructorDemo() {
System.out.println("我被執行了。。。");
}
@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();
}
}
執行結果如下:
我被執行了。。。
就輸出一次,這裡可以證明對象拷貝的時候構造函數是不會執行的,原因在於拷貝是直接在堆中進行,這其實也可以理解, new 的時候, JVM 要走一趟類載入流程,這個流程非常麻煩,在類載入流程中會調用構造函數,最後生成的對象會放到堆中,而拷貝就是直接拷貝堆中的現成的二進位對象,然後重新一個分配記憶體塊。
5. 淺拷貝和深拷貝
先看一個淺拷貝的案例:
public class ShallowCopy implements Cloneable {
private ArrayList<String> 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<String> 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());
}
}
執行的結果是:
[123, 456]
這種情況就是淺拷貝, Java 只拷貝你指定的對象,至於你指定的對象裡面的別的對象,它不拷貝,還是把引用給你,共享變數,這是一種非常不安全的方式,需要特別注意。
內部的數組和引用對象不會拷貝,其他的原始基本類型和 String 類型會被拷貝。
那麼這種情況如何進行一個深拷貝呢?只需要修改一下剛才 clone 的方法:
// 深拷貝
@Override
public ShallowCopy clone() {
ShallowCopy copy = null;
try {
copy = (ShallowCopy) super.clone();
this.array = (ArrayList<String>) this.array.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return copy;
}
還是剛才的測試類,這次的運行結果是:
[123]