深入理解Java設(shè)計(jì)模式之原型模式
一、前言
單例模式可以避免重復(fù)創(chuàng)建消耗資源的對(duì)象,但是卻不得不共用對(duì)象。若是對(duì)象本身也不讓隨意訪問(wèn)修改時(shí),怎么辦?通常做法是備份到副本,其它對(duì)象操作副本,最后獲取權(quán)限合并,類(lèi)似git上的PR操作。
二、什么是原型模式
原型模式用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。需要注意的關(guān)鍵字是,新的對(duì)象,類(lèi)沒(méi)變。.NET在System命名空間中提供了Cloneable接口,其中它提供唯一的方法Clone(),只需要實(shí)現(xiàn)這個(gè)接口就可以完成原型模式了。由于它直接操作內(nèi)存中的二進(jìn)制流,當(dāng)大量操作或操作復(fù)雜對(duì)象時(shí),性能優(yōu)勢(shì)將會(huì)很明顯。
三、原型模式的適用場(chǎng)景
多用于創(chuàng)建大對(duì)象,或初始化繁瑣的對(duì)象。如游戲中的背景,地圖。web中的畫(huà)布等等
以下場(chǎng)景適用:
一是類(lèi)初始化需要消化非常多的資源,這個(gè)資源包括數(shù)據(jù)、硬件資源等;
二是通過(guò) new 產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式;
三是一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以考慮使用原型模式拷貝多個(gè)對(duì)象供調(diào)用者使用。
在實(shí)際項(xiàng)目中,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn),通過(guò) clone的方法創(chuàng)建一個(gè)對(duì)象,然后由工廠方法提供給調(diào)用者。
四、原型模式的實(shí)現(xiàn)
以簡(jiǎn)歷的復(fù)印來(lái)舉例
1.淺拷貝實(shí)現(xiàn)
定義工作經(jīng)歷類(lèi)
/// <summary> /// 工作經(jīng)歷類(lèi) /// </summary> public class WorkExperience { private string _workDate; public string WorkDate { get { return _workDate; } set { _workDate = value; } } private string _company; public string Company { get { return _company; } set { _company = value; } } }
定義簡(jiǎn)歷類(lèi)
/// <summary> /// 簡(jiǎn)歷類(lèi) /// </summary> class Resume : ICloneable { private string name; private string sex; private string age; private WorkExperience work; public Resume(string name) { this.name = name; work = new WorkExperience(); } /// <summary> /// 設(shè)置個(gè)人信息 /// </summary> /// <param name="sex"></param> /// <param name="age"></param> public void SetPersonalInfo(string sex, string age) { this.sex = sex; this.age = age; } /// <summary> /// 設(shè)置工作經(jīng)歷 /// </summary> /// <param name="workDate"></param> /// <param name="company"></param> public void SetWorkExperience(string workDate, string company) { work.WorkDate = workDate; work.Company = company; } /// <summary> /// 顯示 /// </summary> public void Display() { Console.WriteLine("{0}{1}{2}", name, sex, age); Console.WriteLine("工作經(jīng)歷:{0}{1}", work.WorkDate, work.Company); } public object Clone() { //創(chuàng)建當(dāng)前object的淺表副本 return (object)this.MemberwiseClone(); } }
客戶(hù)端調(diào)用
static void Main(string[] args) { Resume a = new Resume("張三"); a.SetPersonalInfo("男", "30"); a.SetWorkExperience("2010-2018", "騰訊公司"); Resume b = (Resume)a.Clone(); b.SetWorkExperience("2010-2015", "阿里公司"); Resume c = (Resume)a.Clone(); c.SetPersonalInfo("女", "18"); c.SetWorkExperience("2010-2015", "百度公司"); a.Display(); b.Display(); c.Display(); Console.Read(); }
結(jié)果
張三 男 30
工作經(jīng)歷 2010-2018 騰訊公司
張三 男 30
工作經(jīng)歷 2010-2018 騰訊公司
張三 女 18
工作經(jīng)歷 2010-2018 騰訊公司
被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值,而所有的對(duì)其他對(duì)象的引用都仍然指向原來(lái)的對(duì)象,這就是淺復(fù)制。但是我們可能需要這樣一種需求,要把復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制一遍。比如剛才的例子,我希望a、b、c三個(gè)引用的對(duì)象都是不同的。復(fù)制時(shí)就一變二,二變?nèi)?。此時(shí),我們就要用的方式叫“深復(fù)制”
2.深拷貝實(shí)現(xiàn)
深復(fù)制把引用對(duì)象的變量指向復(fù)制過(guò)的新對(duì)象,而不是原來(lái)被引用的對(duì)象
/// <summary> /// 工作經(jīng)歷類(lèi) /// </summary> public class WorkExperience:ICloneable { private string _workDate; public string WorkDate { get { return _workDate; } set { _workDate = value; } } private string _company; public string Company { get { return _company; } set { _company = value; } } public object Clone() { //創(chuàng)建當(dāng)前object的淺表副本 return (object)this.MemberwiseClone(); } }
/// <summary> /// 簡(jiǎn)歷類(lèi) /// </summary> class Resume : ICloneable { private string name; private string sex; private string age; private WorkExperience work; public Resume(string name) { this.name = name; work = new WorkExperience(); } private Resume(WorkExperience work) { //提供Clone方法調(diào)用的私有構(gòu)造函數(shù),以便克隆“工作經(jīng)歷”數(shù)據(jù) this.work = (WorkExperience)work.Clone(); } /// <summary> /// 設(shè)置個(gè)人信息 /// </summary> /// <param name="sex"></param> /// <param name="age"></param> public void SetPersonalInfo(string sex, string age) { this.sex = sex; this.age = age; } /// <summary> /// 設(shè)置工作經(jīng)歷 /// </summary> /// <param name="workDate"></param> /// <param name="company"></param> public void SetWorkExperience(string workDate, string company) { work.WorkDate = workDate; work.Company = company; } /// <summary> /// 顯示 /// </summary> public void Display() { Console.WriteLine("{0}{1}{2}", name, sex, age); Console.WriteLine("工作經(jīng)歷:{0}{1}", work.WorkDate, work.Company); } public object Clone() { //調(diào)用私有的構(gòu)造方法,讓“工作經(jīng)歷”克隆完成,然后再給這個(gè)簡(jiǎn)歷對(duì)象的相關(guān)字段賦值, //最終返回一個(gè)深復(fù)制的簡(jiǎn)歷對(duì)象 Resume obj = new Resume(this.work); obj.name = this.name; obj.sex = this.sex; obj.age = this.age; return obj; } }
客戶(hù)端調(diào)用代碼一樣
結(jié)果
張三 男 30
工作經(jīng)歷 2010-2018 騰訊公司
張三 男 30
工作經(jīng)歷 2010-2015 阿里公司
張三 女 18
工作經(jīng)歷 2010-2015 百度公司
由于在一些特定場(chǎng)合,會(huì)經(jīng)常涉及深復(fù)制和淺復(fù)制,比如說(shuō),數(shù)據(jù)集對(duì)象DataSet,它就有Clone()方法和Copy()方法,Clone()方法用來(lái)復(fù)制DataSet的結(jié)構(gòu),但不復(fù)制DataSet的數(shù)據(jù),實(shí)現(xiàn)了原型模式的淺復(fù)制,
Copy()方法不但復(fù)制結(jié)構(gòu),還復(fù)制數(shù)據(jù),其實(shí)就是實(shí)現(xiàn)了原型模式的深復(fù)制。
五、總結(jié)
原型模式通過(guò)Object的clone()方法實(shí)現(xiàn),由于是內(nèi)存操作,無(wú)視構(gòu)造方法和訪問(wèn)權(quán)限,直接獲取新的對(duì)象。但對(duì)于引用類(lèi)型,需使用深拷貝,其它淺拷貝即可。
相關(guān)文章
Java設(shè)計(jì)模式模板方法(Template)原理解析
這篇文章主要介紹了Java設(shè)計(jì)模式模板方法(Template)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11SpringBoot實(shí)現(xiàn)二維碼掃碼登錄的原理及項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot實(shí)現(xiàn)二維碼掃碼登錄的原理及項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04Spring Cloud Zuul添加過(guò)濾器過(guò)程解析
這篇文章主要介紹了Spring Cloud Zuul添加過(guò)濾器過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12springboot返回值轉(zhuǎn)成JSONString的處理方式
這篇文章主要介紹了springboot返回值轉(zhuǎn)成JSONString的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06springboot?正確的在異步線程中使用request的示例代碼
這篇文章主要介紹了springboot中如何正確的在異步線程中使用request,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07Springboot2.6.x的啟動(dòng)流程與自動(dòng)配置詳解
這篇文章主要給大家介紹了關(guān)于Springboot2.6.x的啟動(dòng)流程與自動(dòng)配置的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01