一窃判、什么是原型模式
原型模式(Prototype)燥爷,用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象谅辣。簡單說修赞,就是從一個對象再創(chuàng)建另外一個可定制的對象,而且不需要知道任何創(chuàng)建細(xì)節(jié)桑阶。
二柏副、原型的引入克隆
現(xiàn)在我們有一個需求,要求有一個簡歷類蚣录,必須要有姓名割择,可以設(shè)置性別和年齡,可以設(shè)置工作經(jīng)歷萎河。最終我們需要寫三份簡歷荔泳。
通常,在不用原型模式的情況下虐杯,是這樣實現(xiàn)的玛歌。方式一:
簡歷類
class Resume
{
private String name;
private String gender;
private String age;
private String timeArea;
private String company;
public Resume(String name){
this.name = name;
}
public void setPersonInfo(String gender,String age){
this.gender = gender;
this.age = age;
}
public void setWorkExperience(String timeArea, String company){
this.timeArea = timeArea;
this.company = company;
}
public void display(){
System.out.print(name +" "+ gender +" "+age);
System.out.print("工作經(jīng)歷 "+ timeArea +" "+company);
}
}
客戶端調(diào)用代碼
static void main(String[] args){//此處也可為for循環(huán)創(chuàng)建
Resume a = new Resume("張三");
a.setPersonInfo("男" , "30");
a.setWorkExperience("2010-2018" , "xx公司");
Resume b = new Resume("張三");
b.setPersonInfo("男" , "30");
b.setWorkExperience("2010-2018" , "xx公司");
Resume c = new Resume("張三");
c.setPersonInfo("男" , "30");
c.setWorkExperience("2010-2018" , "xx公司");
a.display();
b.display();
c.display();
}
結(jié)果顯示
張三 男 30
工作經(jīng)歷 2010-2018 xx公司
張三 男 30
工作經(jīng)歷 2010-2018 xx公司
張三 男 30
工作經(jīng)歷 2010-2018 xx公司
這里我們看到3份簡歷需要三次實例化,這樣是不是有點(diǎn)麻煩擎椰,如果需要100份沾鳄,那我們就要實例化100次。一旦寫錯了一個字确憨,都要改100次译荞。
當(dāng)然我們想偷懶一下,可以這樣寫休弃。
Resume a = new Resume("張三");
a.setPersonInfo("男" , "30");
a.setWorkExperience("2010-2018" , "xx公司");
Resume b = a;//傳引用而不傳值
Resume c = a;
a.display();
b.display();
c.display();
以上創(chuàng)建對象是通過new方式創(chuàng)建的吞歼,但是在我們熟知的概念中,還有一種創(chuàng)建對象的方式是克隆方法塔猾。顧名思義篙骡,是通過已有的對象復(fù)制一個新的對象一模一樣的。即我們上面原型模式的定義,所以clone方法是一種原型模式設(shè)計模式糯俗。下面我們來看一下尿褪,克隆方法的使用示例。
//注意此種方式得湘,叫引用復(fù)制杖玲,得到的對象與原來對象一樣,而非創(chuàng)建一個新對象
Resume r = new Resume("張三");
Resume b = r ;
方式二:讓被復(fù)制的對象類繼承接口Cloneable(System已經(jīng)提供了)淘正,并重寫clone()方法摆马。
public class Resume implements Cloneable {
private String name;
private String gender;
private String age;
private String timeArea;
private String company;
public Resume(String name) {
this.name = name;
}
public void setPersonInfo(String gender, String age) {
this.gender = gender;
this.age = age;
}
public void setWorkExperience(String timeArea, String company) {
this.timeArea = timeArea;
this.company = company;
}
public void display() {
System.out.print(name +" "+ gender +" "+age);
System.out.print("工作經(jīng)歷 "+ timeArea +" "+company);
}
//只需要調(diào)用此方法就可以實現(xiàn)新簡歷(對象)的生成,且可以再次改新簡歷的細(xì)節(jié)
@Override
protected Object clone() throws CloneNotSupportedException {
return (Resume) super.clone();
}
}
調(diào)用代碼
Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "xx公司");
Resume cloneResume = (Resume) resume.clone() ;
cloneResume.setPersonInfo("女", "21");
Resume cloneResume2 = (Resume) resume.clone() ;
cloneResume2.setWorkExperience("2015-2018", "xx公司");
結(jié)果為:
張三豐 男 30
工作經(jīng)歷 2010-1018 xx公司
張三豐 女 21
工作經(jīng)歷 2010-1018 xx公司
張三豐 男 30
工作經(jīng)歷 2015-2018 xx公司
兩種方法都可以實現(xiàn)鸿吆。但是方式一每new一次囤采,就需要執(zhí)行一次構(gòu)造函數(shù),如果構(gòu)造行數(shù)的執(zhí)行時間很長惩淳,那么多次的執(zhí)行初始化操作就太低效率了蕉毯。一般在初始化的信息不發(fā)生變化的情況下,方式二克隆是最好的辦法思犁。這既隱藏了對象的創(chuàng)建細(xì)節(jié)恕刘,又對性能是大大的提高。不用重新初始化對象抒倚,動態(tài)獲得對象運(yùn)行時的狀態(tài)褐着。
三、淺復(fù)制與深復(fù)制
上面的例子托呕,對象的屬性都是String類型含蓉,而String類型是一種擁有值類型特點(diǎn)的特殊引用類型。clone()方法是這樣的项郊,如果字段是值類型的馅扣,則對該字段執(zhí)行逐位復(fù)制,如果字段是引用類型着降,則復(fù)制引用但不復(fù)制引用的對象差油,因此,原始對象及其復(fù)本引用同一對象任洞。以簡歷的例子講蓄喇,假設(shè)WorkExperience作為簡歷類的屬性,那么在克隆后交掏,會把引用對象中的數(shù)據(jù)克隆過來嗎妆偏?
public class Resume implements Cloneable {
private String name;
private String gender;
private String age;
private WorkExperience experience;
public Resume(String name) {
this.name = name;
experience = new WorkExperience();
}
public void setPersonInfo(String gender, String age) {
this.gender = gender;
this.age = age;
}
public void setWorkExperience(String timeArea, String company) {
experience.setWorkDate(timeArea);
experience.setCompany(company);
}
public void display() {
System.out.print(name +" "+ gender +" "+age);
System.out.print("工作經(jīng)歷 " + experience.getWorkDate() + " " +experience.getCompany());
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Resume) super.clone();
}
}
工作經(jīng)驗類
public class WorkExperience {
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
調(diào)用代碼
Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "麥當(dāng)勞公司");
Resume cloneResume = resume.clone() ;
cloneResume.setPersonInfo("男", "22");
cloneResume.setWorkExperience("2017-1018", "肯德基公司");
Resume cloneResume2 = (Resume)resume.clone();
cloneResume2.setPersonInfo("女", "24");
cloneResume2.setWorkExperience("2015-2018", "德克士公司");
resume.display();
cloneResume.display();
cloneResume2.display();
結(jié)果顯示:
張三豐 男 30
工作經(jīng)歷 2015-2018 德克士公司
張三豐 男 22
工作經(jīng)歷 2015-2018 德克士公司
張三豐 女 24
工作經(jīng)歷 2015-2018 德克士公司
并沒有我們想要的結(jié)果,三次顯示都是最后一次設(shè)置的值盅弛。通過查幫助文檔钱骂,我們知道clone()方法是淺表復(fù)制叔锐,對于值類型,沒有什么問題见秽,但對于引用類型愉烙,就只是復(fù)制了引用,還是指向了原來的對象解取,所以就會出現(xiàn)上面的結(jié)果步责,三次結(jié)果相同。
淺復(fù)制肮蛹,被復(fù)制對象的所有變量都含有與原來的對象相同的值勺择,而所有的對其他對象的引用都仍然指向原來的對象创南。但我們可能更需要這樣的一種需求伦忠,把要復(fù)制的對象所引用的對象都復(fù)制一遍。比如剛才的例子稿辙,我們希望是a,b,c三個引用的對象都是不同的昆码,復(fù)制時就一變二,二變?nèi)诖ⅲ藭r赋咽,我們就叫這種方式為‘深復(fù)制’,深復(fù)制把引用對象的變量指向復(fù)制過的新對象吨娜,而不是原有的被引用的對象脓匿。
繼續(xù)改造上面淺復(fù)制為深復(fù)制
public class Resume implements Cloneable {
private String name;
private String gender;
private String age;
private WorkExperience experience;
public Resume(String name) {
this.name = name;
experience = new WorkExperience();
}
private Resume(WorkExperience experience) {
this.experience = (WorkExperience) experience.clone();
}
public void setPersonInfo(String gender, String age) {
this.gender = gender;
this.age = age;
}
public void setWorkExperience(String timeArea, String company) {
experience.setWorkDate(timeArea);
experience.setCompany(company);
}
public void display() {
System.out.print(name +" "+ gender +" "+age);
System.out.print("工作經(jīng)歷 " + experience.getWorkDate() + " " +experience.getCompany());
}
@Override
protected Object clone(){
Resume r = new Resume(this.experience);
r.name = this.name;
r.gender = this.gender;
r.age = this.age;
return r;
}
}
工作經(jīng)驗類改造,繼承克隆接口宦赠,重寫clone方法
public class WorkExperience implements Cloneable {
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (WorkExperience) super.clone();
}
}
調(diào)用代碼
Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "麥當(dāng)勞公司");
Resume cloneResume = resume.clone() ;
cloneResume.setPersonInfo("男", "22");
cloneResume.setWorkExperience("2017-1018", "肯德基公司");
Resume cloneResume2 = (Resume)resume.clone();
cloneResume2.setPersonInfo("女", "24");
cloneResume2.setWorkExperience("2015-2018", "德克士公司");
resume.display();
cloneResume.display();
cloneResume2.display();
結(jié)果顯示:
張三豐 男 30
工作經(jīng)歷 2010-1018 麥當(dāng)勞公司
張三豐 男 22
工作經(jīng)歷 2017-1018 肯德基公司
張三豐 女 24
工作經(jīng)歷 2015-2018 德克士公司
這就達(dá)到了我們想要的結(jié)果陪毡,三份不同的簡歷。由于在一些特定場合勾扭,會經(jīng)常涉及深復(fù)制或淺復(fù)制毡琉,比如說數(shù)據(jù)集對象DataSet,它就有clone()方法和copy()方法妙色,clone方法用來復(fù)制DataSet的結(jié)構(gòu)桅滋,但不復(fù)制DataSet的數(shù)據(jù),實現(xiàn)了原型模式的淺復(fù)制身辨。copy方法不但復(fù)制結(jié)構(gòu)丐谋,也復(fù)制數(shù)據(jù),其實就是實現(xiàn)了原型模式的深復(fù)制煌珊。
四笋鄙、使用場景:
1、資源優(yōu)化場景怪瓶。
2萧落、類初始化需要消化非常多的資源践美,這個資源包括數(shù)據(jù)、硬件資源等找岖。
3陨倡、性能和安全要求的場景。
4许布、通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限兴革,則可以使用原型模式。
5蜜唾、一個對象多個修改者的場景杂曲。
6、一個對象需要提供給其他對象訪問袁余,而且各個調(diào)用者可能都需要修改其值時擎勘,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用。
7颖榜、在實際項目中棚饵,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn)掩完,通過 clone 的方法創(chuàng)建一個對象噪漾,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體且蓬,大家可以隨手拿來使用欣硼。
五、注意事項:
與通過對一個類進(jìn)行實例化來構(gòu)造新對象不同的是恶阴,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的诈胜。淺拷貝實現(xiàn) Cloneable,重寫clone方法存淫。深復(fù)制是耘斩,采用對象的序列化Serializable技術(shù)進(jìn)行clone,對象的序列化會將對象以及對象的句柄的內(nèi)容都序列化,然后輸出到對象的流中桅咆,可以將流直接保存到內(nèi)存二級制流中括授,然后再用對象輸入流從內(nèi)存二進(jìn)制中讀取該流,再轉(zhuǎn)換成相應(yīng)的對象岩饼,這樣就完成了非常復(fù)雜而且是深層次的復(fù)制荚虚。