設計模式:原型模式(物件構造方法之前賦值,構造方法巢狀,克隆)
技術標籤:設計模式
原型模式主要解決的問題就是建立重複物件,而這部分物件內容本身⽐較複雜,生成過程可能從庫或者RPC接⼝(遠端介面呼叫)中獲取資料的耗時較長,因此採用克隆的方式節省時間,clone方法最終將呼叫JVM中的原生方法完成複製也就是呼叫底層的c++程式碼.
根據自己的開發業務經驗感覺此模式主要適用於物件大部分屬性相同,但部分屬性不同的場景.比如說考試的試卷,每個人的考題是相同的,但是姓名和學號是不同的.就這一場景進行模擬一下 .要求:小明 小紅輸入姓名以及學號之後就能看到自己的考題內容.
考題單元內容:
public class TestQuestionsUnit {
// 題目
private String question;
// 題目選項內容
private Map<String,String> questionMap;
// 答案
private String answer;
// 省略get/set..
@Override
public String toString() {
return "TestQuestions{" +
"question='" + question + '\'' +
", questionMap=" + questionMap +
", answer='" + answer + '\'' +
'}';
}
}
試卷:
public class TestQuestions {
// 姓名
private String name;
// 考試編號
private String no;
// 考試題目集合
private List<TestQuestionsUnit> testQuestionsUnitList;
// 省略get/set..
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
}
分發考卷1.0:
public static void main(String[] args) {
// 給小明建立考卷
// 構造題庫
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一題
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸點是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97攝氏度");
hashMap.put("B","98攝氏度");
hashMap.put("C","99攝氏度");
hashMap.put("D","100攝氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二題
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少個小時( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap2.put("A","48");
hashMap2.put("B","36");
hashMap2.put("C","24");
hashMap2.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap2);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三題
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有雞還是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
TestQuestions testQuestions = new TestQuestions();
testQuestions.setName("小明");
testQuestions.setNo("0001");
testQuestions.setTestQuestionsUnitList(testQuestionsUnits);
System.out.println("小明試卷:"+testQuestions);
// 給小紅建立考卷
// 構造題庫
ArrayList<TestQuestionsUnit> testQuestionsUnits2 = new ArrayList<>();
// 第一題
TestQuestionsUnit testQuestionsUnit21 = new TestQuestionsUnit();
testQuestionsUnit21.setQuestion("水的沸點是多少度( )");
HashMap<String, String> hashMap21 = new HashMap<>();
hashMap21.put("A","97攝氏度");
hashMap21.put("B","98攝氏度");
hashMap21.put("C","99攝氏度");
hashMap21.put("D","100攝氏度");
testQuestionsUnit21.setQuestionMap(hashMap21);
testQuestionsUnits2.add(testQuestionsUnit21);
// 第二題
TestQuestionsUnit testQuestionsUnit22 = new TestQuestionsUnit();
testQuestionsUnit22.setQuestion("一天有多少個小時( )");
HashMap<String, String> hashMap22 = new HashMap<>();
hashMap22.put("A","48");
hashMap22.put("B","36");
hashMap22.put("C","24");
hashMap22.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap22);
testQuestionsUnits2.add(testQuestionsUnit2);
// 第三題
TestQuestionsUnit testQuestionsUnit23 = new TestQuestionsUnit();
testQuestionsUnit23.setQuestion("先有雞還是先有蛋?");
testQuestionsUnits2.add(testQuestionsUnit23);
TestQuestions testQuestions2 = new TestQuestions();
testQuestions2.setName("小紅");
testQuestions2.setNo("0002");
testQuestions2.setTestQuestionsUnitList(testQuestionsUnits2);
System.out.println("小紅試卷:"+testQuestions2);
}
分發考卷1.0輸出內容:
小明試卷:TestQuestions{name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
小紅試卷:TestQuestions{name='小紅', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
發現試題內容都是相同的,所以考慮將試題內容在建立考卷的初始化中將試題建立完成,這裡採用靜態程式碼塊的方式在考卷構造方法之前將考題內容載入到考卷中,所以有了考卷2.0:
public class TestQuestions {
// 初始化考題內容
static {
// 構造方法之前初始化題庫
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一題
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸點是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97攝氏度");
hashMap.put("B","98攝氏度");
hashMap.put("C","99攝氏度");
hashMap.put("D","100攝氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二題
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少個小時( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap.put("A","48");
hashMap.put("B","36");
hashMap.put("C","24");
hashMap.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三題
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有雞還是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
TestQuestions.testQuestionsUnitList=testQuestionsUnits;
}
// 姓名
private String name;
// 考試編號
private String no;
// 考試題目集合,此處應為靜態變數,否則無法在靜態程式碼塊中呼叫
private static List<TestQuestionsUnit> testQuestionsUnitList;
// 無參構造
public TestQuestions() {
}
// 全參構造(兩個引數)
public TestQuestions(String name, String no) {
this.name = name;
this.no = no;
new TestQuestions(name,no,testQuestionsUnitList); // 呼叫三個引數的構造方法,或是使用下面的呼叫三個引數的構造方法(必須保證在構造方法的第一行,否則idea會報錯)
//this(name,no,testQuestionsUnitList);
}
// 全參構造(三個引數)
public TestQuestions(String name, String no,List<TestQuestionsUnit> testQuestionsUnitList) {
this.name = name;
this.no = no;
}
// 省略get/set....
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
}
分發考卷2.0:
public static void main(String[] args) {
TestQuestions te = new TestQuestions("小明", "001");
TestQuestions te1 = new TestQuestions("小紅", "002");
System.out.println(te);
System.out.println(te1);
}
輸出結果:
小明試卷:TestQuestions{name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
小紅試卷:TestQuestions{name='小紅', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
是不是感覺清爽多了,但是使用new的方式建立大量物件(屬性大部分相同)耗時是非常多的,原型模式解決的建立相同物件的耗時問題.原因是原型模式中呼叫clone方法,clone比new優越性在於:new物件需要執行構造方法進行初始化操作,而clone方法不需要呼叫構造方法,執行效率上clone要更高;
考卷3.0(原型模式):
public class TestQuestionsClone implements Cloneable {
static {
System.out.println("靜態程式碼塊中邏輯執行");
// 構造方法之前初始化題庫
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一題
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸點是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97攝氏度");
hashMap.put("B","98攝氏度");
hashMap.put("C","99攝氏度");
hashMap.put("D","100攝氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二題
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少個小時( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap.put("A","48");
hashMap.put("B","36");
hashMap.put("C","24");
hashMap.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三題
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有雞還是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
System.out.println("靜態程式碼塊中執行題庫初始化:"+testQuestionsUnits);
TestQuestionsClone.testQuestionsUnitList=testQuestionsUnits;
System.out.println("靜態程式碼塊中給題庫進行賦值結束");
}
// 姓名
private String name;
// 考試編號
private String no;
// 考試題目集合
private static List<TestQuestionsUnit> testQuestionsUnitList;
public TestQuestionsClone(String name, String no) {
this.name = name;
this.no = no;
new TestQuestions(name,no,testQuestionsUnitList); // 呼叫三個引數的構造方法,或是使用下面的呼叫三個引數的構造方法(必須保證在第一行,否則需要報錯)
//this(name,no,testQuestionsUnitList);
}
public TestQuestionsClone(String name, String no,List<TestQuestionsUnit> testQuestionsUnitList) {
this.name = name;
this.no = no;
}
// 省略get/set方法
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
// 繼承cloneable介面,重寫clone方法
@Override
public TestQuestionsClone clone() throws CloneNotSupportedException {
return (TestQuestionsClone)super.clone();
}
}
分發考卷3.0:
public static void main(String[] args) throws CloneNotSupportedException {
TestQuestionsClone testQuestionsClone = new TestQuestionsClone("小明", "001");
System.out.println(testQuestionsClone);
TestQuestionsClone testQuestionsClone2 = testQuestionsClone.clone();
testQuestionsClone2.setName("小紅");
testQuestionsClone2.setNo("002");
System.out.println(testQuestionsClone2);
}
輸出結果:
小明試卷:TestQuestions{name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
小紅試卷:TestQuestions{name='小紅', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸點是多少度( )', questionMap={A=97攝氏度, B=98攝氏度, C=99攝氏度, D=100攝氏度}, answer='null'}, TestQuestions{question='一天有多少個小時( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有雞還是先有蛋?', questionMap=null, answer='null'}]'}
實際上就是在2.0的基礎上讓試卷物件繼承cloneable介面重寫clone方法.第一次使用new的方式進行建立,之後的每一次都使用克隆的方法進行建立;
下面測試new物件與clone物件的效率:
public static void main(String[] args) throws CloneNotSupportedException {
// 原型模式clone方法
long start = System.currentTimeMillis();
TestQuestionsClone testQuestionsClone = new TestQuestionsClone("小明0", "1");
for (int i = 0; i < 1000; i++) {
TestQuestionsClone testQuestionsClone2 = testQuestionsClone.clone();
testQuestionsClone2.setName("小明"+String.valueOf(++i));
}
long end = System.currentTimeMillis();
System.out.println(end-start); // 耗時:2
// new的方式建立物件
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
TestQuestions te = new TestQuestions("小明", String.valueOf(i));
}
long end = System.currentTimeMillis();
System.out.println(end-start); // 耗時:19
}
說到clone簡單說一下深克隆與淺克隆
淺克隆:建立新的物件,地址值改變,複製物件的基本型別值,如果物件中引用型別,引用還是指向原來的引用物件;考卷3.0中實際上是屬於淺克隆,可以列印一下TestQuestionsClone 中的引用testQuestionsUnitList的地址值,克隆出來的地址值與首次建立的TestQuestionsClone 中的引用testQuestionsUnitList的地址值相同;
深克隆:建立新的物件,地址值改變,複製物件的基本型別,如果物件有引用型別,則建立新的引用物件.深克隆與淺克隆的區別在於物件中存在引用的情況,實現深克隆可以將引用中的物件實現cloneable重寫clone方法或是實現Serializable介面.