【設(shè)計(jì)模式】原來這就是原型模式
原型模式介紹
概述
原型模式顧名思義,就是基于原型來創(chuàng)建對(duì)象,用人話說就是一個(gè)對(duì)象的產(chǎn)生可以不由零起步,直接從一個(gè)已經(jīng)具備一定雛形的對(duì)象克隆,然后再修改為所需要的對(duì)象。顯而易見 ,原型模式屬于創(chuàng)建型模式,
使用場(chǎng)景
如果對(duì)象的創(chuàng)建成本比較大,例如某個(gè)對(duì)象里面的數(shù)據(jù)需要訪問數(shù)據(jù)庫(kù)才能拿到;并且同一個(gè)類的不同對(duì)象之間差別不大(大部分字段都相同),這種場(chǎng)景下可以考慮使用原型模式達(dá)到提高程序性能的作用。
我們可以通過一個(gè)業(yè)務(wù)場(chǎng)景來理解原型模式的應(yīng)用,設(shè)計(jì)一個(gè)學(xué)生類,學(xué)生類主要的成員變量有名字name,班級(jí)classId, needExtraCourse,以及所學(xué)的課程 course,其中course需要通過rpc調(diào)用查詢課程系統(tǒng)獲取,這個(gè)耗時(shí)100ms。如果用普通的方式為該類創(chuàng)建同一個(gè)班的4個(gè) 對(duì)象,如下所示:
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
class Student{
private String name;
private Integer classId;
//是否需要額外多選課程
private Boolean needExtraCourse = false;
//所學(xué)課程,需要通過rpc調(diào)用查詢課程系統(tǒng),耗時(shí)100ms
private List<String> course = new ArrayList<>();
public Student(String name, Integer classId) throws InterruptedException {
this.name = name;
this.classId = classId;
//超時(shí)模擬rpc調(diào)用獲取課程信息初始化course
Thread.sleep(100);
course.add("語(yǔ)文");
course.add("數(shù)學(xué)");
course.add("英語(yǔ)");
}
//getter、setter、toString函數(shù)省略
}
public class PrototypeExample {
public static void main(String[] args) throws InterruptedException {
long start = new Date().getTime();
Student student1 = new Student("張三", 1);
Student student2 = new Student("李四", 1);
Student student3 = new Student("王五", 1);
Student student4 = new Student("趙六", 1);
long end = new Date().getTime();
System.out.println("創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:" + (end -start) + " ms");
}
}
******************【運(yùn)行結(jié)果】******************
創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:413ms
只是創(chuàng)建了4個(gè)對(duì)象就花費(fèi)了400多ms,這樣成本也太大了,其實(shí)對(duì)于一個(gè)班級(jí)的學(xué)生,不考慮額外選修課程的情況下(needExtraCourse = false),所學(xué)的課程應(yīng)該是完全一樣的。
因此可以用到今天介紹的原型模式:先用普通的方式創(chuàng)建一個(gè)對(duì)象,然后從創(chuàng)建的對(duì)象中克隆出其它對(duì)象,再修改其它對(duì)象的name字段即可。
UML類圖

UML 類圖也比較簡(jiǎn)單,只有兩個(gè)部分:
? 1.Cloneable接口
? 2.Student類,實(shí)現(xiàn)了Cloneable接口的原型對(duì)象,這個(gè)對(duì)象有個(gè)能力就是可以克隆自己。
原型模式實(shí)現(xiàn)
原型模式的實(shí)現(xiàn)方式有兩種:淺拷貝和深拷貝。關(guān)于淺拷貝和深拷貝,可以閱讀詳解淺拷貝與深拷貝
淺拷貝模式
淺拷貝僅僅復(fù)制所考慮的對(duì)象,而不復(fù)制它所引用的對(duì)象。Object類提供的方法clone只是拷貝本對(duì)象 , 其對(duì)象內(nèi)部的數(shù)組、引用對(duì)象等都不拷貝,還是指向原生對(duì)象的內(nèi)部元素地址,下面是淺拷貝原型模式實(shí)現(xiàn)的代碼:
//1原型類需實(shí)現(xiàn)Cloneable接口
class Studentimplements Cloneable{
//Student類的其余部分和上面例子一樣
......................@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class PrototypeExample {
public static void main(String[] args) throws InterruptedException, CloneNotSupportedException {
long start = new Date().getTime();
Student student1 = new Student("張三", 1);
Student student2 = (Student) student1.clone();
student2.setName("李四");
Student student3 = (Student) student1.clone();
student3.setName("王五");
Student student4 = (Student) student1.clone();
student4.setName("趙六 ");
System.out.println("student1--->" + student1);
System.out.println("student2--->" + student2);
System.out.println("student3--->" + student3);
System.out.println("student4--->" + student4);
long end = new Date().getTime();
System.out.println("創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:" + (end -start) + " ms");
//趙六選修了課程美術(shù)
student4.setNeedExtraCourse(true);
student4.getCourse().add("美術(shù) ");
System.out.println("\n趙六選修美術(shù)課后:");
System.out.println("student1--->" + student1);
System.out.println("student2--->" + student2);
System.out.println("student3--->" + student3);
System.out.println("student4--->" + student4);
}
}
******************【運(yùn)行結(jié)果】******************
student1--->Student{name='張三', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student4--->Student{name='趙六 ', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:130ms
趙六選修美術(shù)課后:
student1--->Student{name='張三', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ),美術(shù)]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ),美術(shù)]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ),美術(shù)]}
student4--->Student{name='趙六 ', classId=1, needExtraCourse=true, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ),美術(shù)]}
可以看到,通過原型模式的方式,同樣是創(chuàng)建四個(gè)對(duì)象,只花了100多ms,大大的提高了程序性能。
但是這里還存在一個(gè)小問題:趙六比較好學(xué),所以還選修了美術(shù),但是通過程序的運(yùn)行結(jié)果可以看到,明明只是趙六選修了美術(shù),但是其他三個(gè)同學(xué)的課表中也都出現(xiàn)了美術(shù)課 。這是因?yàn)闇\拷貝雖然產(chǎn)生了兩個(gè)完全不同的對(duì)象,但是對(duì)象中有對(duì)其他對(duì)象的引用(如這里的List)都指向同一個(gè)對(duì)象。
為了解決這個(gè)問題,我們引入了深拷貝模式。
深拷貝模式
深拷貝模式把要復(fù)制的對(duì)象所引用的對(duì)象都拷貝了一遍。
class Studentimplements Cloneable{
//Student類的其余部分和上面例子一樣
......................
@Override
public Object clone() throws CloneNotSupportedException {
Object object = super.clone();
Student student = (Student)object;
List<String> newCourse = new ArrayList<>();
Iterator<String> it = student.course.iterator();
while (it.hasNext()) {
newCourse.add(it.next());
}
student.course = newCourse;
return object;
}
}
public class PrototypeExample {
public static void main(String[] args) throws InterruptedException, CloneNotSupportedException {
long start = new Date().getTime();
Student student1 = new Student("張三", 1);
Student student2 = (Student) student1.clone();
student2.setName("李四");
Student student3 = (Student) student1.clone();
student3.setName("王五");
Student student4 = (Student) student1.clone();
student4.setName("趙六 ");
System.out.println("student1--->" + student1);
System.out.println("student2--->" + student2);
System.out.println("student3--->" + student3);
System.out.println("student4--->" + student4);
long end = new Date().getTime();
System.out.println("創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:" + (end -start) + " ms");
//趙六選修了課程美術(shù)
student4.setNeedExtraCourse(true);
student4.getCourse().add("美術(shù) ");
System.out.println("\n趙六選修美術(shù)課后:");
System.out.println("student1--->" + student1);
System.out.println("student2--->" + student2);
System.out.println("student3--->" + student3);
System.out.println("student4--->" + student4);
}
}
******************【運(yùn)行結(jié)果】******************
student1--->Student{name='張三', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student4--->Student{name='趙六 ', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
創(chuàng)建對(duì)象共花費(fèi)了時(shí)間:135ms
趙六選修美術(shù)課后:
student1--->Student{name='張三', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student2--->Student{name='李四', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student3--->Student{name='王五', classId=1, needExtraCourse=false, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ)]}
student4--->Student{name='趙六 ', classId=1, needExtraCourse=true, course=[語(yǔ)文, 數(shù)學(xué), 英語(yǔ),美術(shù)]}
可以看到,深拷貝模式 ,修改一個(gè)對(duì)象的引用類型的成員不會(huì)再影響另外對(duì)象的該成員了。
本文源碼地址:
https://github.com/qinlizhong1/javaStudy/tree/master/DesignPattern/src/prototype
本文示例代碼環(huán)境:
操作系統(tǒng):macOs 12.1
JDK版本:12.0.1
maven版本: 3.8.4
— 完 —
