Java設(shè)計(jì)模式之java原型模式詳解
介紹
原型模式(Prototype Pattern
):使用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過拷貝這些原型創(chuàng)建新的對(duì)象。原型模式是一種對(duì)象創(chuàng)建型模式。
原型模式的工作原理很簡單:將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過請(qǐng)求原型對(duì)象拷貝自己來實(shí)現(xiàn)創(chuàng)建過程。
原型模式是一種“另類”的創(chuàng)建型模式,創(chuàng)建克隆對(duì)象的工廠就是原型類自身,工廠方法由克隆方法來實(shí)現(xiàn)。
需要注意的是通過克隆方法所創(chuàng)建的對(duì)象是全新的對(duì)象,它們在內(nèi)存中擁有新的地址,通常對(duì)克隆所產(chǎn)生的對(duì)象進(jìn)行修改對(duì)原型對(duì)象不會(huì)造成任何影響,每一個(gè)克隆對(duì)象都是相互獨(dú)立的。通過不同的方式修改可以得到一系列相似但不完全相同的對(duì)象。
角色
Prototype
(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至還可以是具體實(shí)現(xiàn)類。ConcretePrototype
(具體原型類):它實(shí)現(xiàn)在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個(gè)克隆對(duì)象。Client
(客戶類):讓一個(gè)原型對(duì)象克隆自身從而創(chuàng)建一個(gè)新的對(duì)象,在客戶類中只需要直接實(shí)例化或通過工廠方法等方式創(chuàng)建一個(gè)原型對(duì)象,再通過調(diào)用該對(duì)象的克隆方法即可得到多個(gè)相同的對(duì)象。由于客戶類針對(duì)抽象原型類Prototype編程,因此用戶可以根據(jù)需要選擇具體原型類,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體原型類都很方便。
原型模式的核心在于如何實(shí)現(xiàn)克隆方法。
Java語言提供的clone()方法
學(xué)過Java語言的人都知道,所有的Java類都繼承自 java.lang.Object。事實(shí)上,Object 類提供一個(gè) clone() 方法,可以將一個(gè)Java對(duì)象復(fù)制一份。因此在Java中可以直接使用 Object 提供的 clone() 方法來實(shí)現(xiàn)對(duì)象的克隆,Java語言中的原型模式實(shí)現(xiàn)很簡單。
需要注意的是能夠?qū)崿F(xiàn)克隆的Java類必須實(shí)現(xiàn)一個(gè) 標(biāo)識(shí)接口 Cloneable,表示這個(gè)Java類支持被復(fù)制。如果一個(gè)類沒有實(shí)現(xiàn)這個(gè)接口但是調(diào)用了clone()方法,Java編譯器將拋出一個(gè) CloneNotSupportedException 異常。
代碼演示—克隆羊
具體原型類:
//實(shí)現(xiàn)Cloneable接口 @Data public class Sheep implements Cloneable { private String name; private Integer age; //重寫Object的clone方法 @Override protected Object clone() throws CloneNotSupportedException { Sheep sheep=null; sheep=(Sheep)super.clone(); return sheep; } }
客戶端創(chuàng)建并克隆原型對(duì)象:
//創(chuàng)建原型對(duì)象 Sheep sheep=new Sheep(); sheep.setAge(3); sheep.setName("肖恩"); //克隆 Sheep sheep1 = sheep.clone(); Sheep sheep2=sheep.clone(); System.out.println(sheep1); System.out.println(sheep2); System.out.println(sheep1==sheep2);
結(jié)論
- 克隆出來的對(duì)象,它們的內(nèi)存地址均不同,說明不是同一個(gè)對(duì)象,克隆成功,克隆僅僅通過調(diào)用 super.clone() 即可。
看一眼 Object#clone 方法
protected native Object clone() throws CloneNotSupportedException;
這是一個(gè) native 關(guān)鍵字修飾的方法
一般而言,Java語言中的clone()方法滿足:
- 對(duì)任何對(duì)象x,都有 x.clone() != x,即克隆對(duì)象與原型對(duì)象不是同一個(gè)對(duì)象;
- 對(duì)任何對(duì)象x,都有 x.clone().getClass() == x.getClass(),即克隆對(duì)象與原型對(duì)象的類型一樣;
- 如果對(duì)象x的 equals() 方法定義恰當(dāng),那么 x.clone().equals(x) 應(yīng)該成立。
- 為了獲取對(duì)象的一份拷貝,我們可以直接利用Object類的clone()方法,具體步驟如下:
在派生類中覆蓋基類的 clone() 方法,并聲明為public;
在派生類的 clone() 方法中,調(diào)用 super.clone();
派生類需實(shí)現(xiàn)Cloneable接口。
此時(shí),Object類相當(dāng)于抽象原型類,所有實(shí)現(xiàn)了Cloneable接口的類相當(dāng)于具體原型類。
深淺拷貝
pig類:
@Data @AllArgsConstructor @NoArgsConstructor public class Pig { String name; Integer age; }
sheep類:
//實(shí)現(xiàn)Cloneable接口 @Data public class Sheep implements Cloneable { private String name; private Integer age; private Pig pig; //重寫Object的clone方法 @Override protected Sheep clone() throws CloneNotSupportedException { Sheep sheep=null; sheep=(Sheep)super.clone(); return sheep; } }
客戶端進(jìn)行克隆:
public class test { @Test public void test() throws CloneNotSupportedException { //創(chuàng)建原型對(duì)象 Sheep sheep=new Sheep(); sheep.setAge(3); sheep.setName("肖恩"); sheep.setPig(new Pig("大忽悠",3)); //克隆 Sheep sheep1 = sheep.clone(); Sheep sheep2=sheep.clone(); System.out.println(sheep1); System.out.println(sheep2); System.out.println(sheep1==sheep2); System.out.println("=============================="); System.out.println(sheep1.getPig()==sheep2.getPig()); } }
這里對(duì)Sheep類里面的引用類型Pig的克隆方式只是簡單的地址拷貝,即淺拷貝操作
深淺拷貝探討
淺克?。?/strong>
- 在淺克隆中,如果原型對(duì)象的成員變量是值類型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。
- 簡單來說,在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量,而引用類型的成員對(duì)象并沒有復(fù)制。
- 在Java語言中,通過覆蓋Object類的clone()方法可以實(shí)現(xiàn)淺克隆
深克?。?/strong>
- 在深克隆中,無論原型對(duì)象的成員變量是值類型還是引用類型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。
- 簡單來說,在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。
- 在Java語言中,如果需要實(shí)現(xiàn)深克隆,可以通過序列化(Serialization)等方式來實(shí)現(xiàn)。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類必須實(shí)現(xiàn)Serializable接口,否則無法實(shí)現(xiàn)序列化操作。
實(shí)現(xiàn)深克隆的方式一 : 手動(dòng)對(duì)引用對(duì)象進(jìn)行克隆
Pig類首先需要實(shí)現(xiàn)克隆即可,并重寫clone方法:
@Data @AllArgsConstructor @NoArgsConstructor public class Pig implements Cloneable { String name; Integer age; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
sheep類:
//實(shí)現(xiàn)Cloneable接口 @Data public class Sheep implements Cloneable { private String name; private Integer age; private Pig pig; //重寫Object的clone方法 @Override protected Sheep clone() throws CloneNotSupportedException { Sheep sheep=null; sheep=(Sheep)super.clone(); sheep.pig=(Pig)sheep.pig.clone(); return sheep; } }
實(shí)現(xiàn)深克隆的方式一 :序列化
對(duì)象可以序列化的前提是實(shí)現(xiàn)了Serializable接口,這里Sheep和Pig都需要實(shí)現(xiàn)該接口
pig類:
@Data @AllArgsConstructor @NoArgsConstructor public class Pig implements Serializable { String name; Integer age; }
sheep類:
//實(shí)現(xiàn)Cloneable接口 @Data public class Sheep implements Serializable { private String name; private Integer age; private Pig pig; //序列化方式完成深拷貝 public Sheep deepClone() throws IOException, ClassNotFoundException { //先將要序列化的對(duì)象寫入流中 ByteArrayOutputStream baot=new ByteArrayOutputStream(); //ObjectOutputStream構(gòu)造函數(shù)的參數(shù)是,將對(duì)象流寫入到哪里 ObjectOutputStream oot=new ObjectOutputStream(baot); oot.writeObject(this); //將序列化的對(duì)象從流中讀取出來 ByteArrayInputStream bait=new ByteArrayInputStream(baot.toByteArray()); ObjectInputStream oit=new ObjectInputStream(bait); return (Sheep) oit.readObject(); } }
原型模式對(duì)單例模式的破壞
餓漢式單例模式如下:
public class HungrySingleton implements Serializable, Cloneable { private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton(); } private HungrySingleton() {} public static HungrySingleton getInstance() { return hungrySingleton; } private Object readResolve() { return hungrySingleton; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
使用反射獲取對(duì)象,測試如下
public class Test { public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { HungrySingleton hungrySingleton = HungrySingleton.getInstance(); Method method = hungrySingleton.getClass().getDeclaredMethod("clone"); method.setAccessible(true); HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton); System.out.println(hungrySingleton); System.out.println(cloneHungrySingleton); } }
輸出
com.designpattern.HungrySingleton@34c45dca
com.designpattern.HungrySingleton@52cc8049
可以看到,通過原型模式,我們把單例模式給破壞了,現(xiàn)在有兩個(gè)對(duì)象了
為了防止單例模式被破壞,我們可以:不實(shí)現(xiàn) Cloneable 接口;或者把 clone 方法改為如下
@Override protected Object clone() throws CloneNotSupportedException { return getInstance(); }
優(yōu)缺點(diǎn)
原型模式的主要優(yōu)點(diǎn)如下:
- 當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí),使用原型模式可以簡化對(duì)象的創(chuàng)建過程,通過復(fù)制一個(gè)已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
- 擴(kuò)展性較好,由于在原型模式中提供了抽象原型類,在客戶端可以針對(duì)抽象原型類進(jìn)行編程,而將具體原型類寫在配置文件中,增加或減少產(chǎn)品類對(duì)原有系統(tǒng)都沒有任何影響。
- 原型模式提供了簡化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級(jí)結(jié)構(gòu)相同的工廠等級(jí)結(jié)構(gòu),而原型模式就不需要這樣,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實(shí)現(xiàn)的,無須專門的工廠類來創(chuàng)建產(chǎn)品。
- 可以使用深克隆的方式保存對(duì)象的狀態(tài),使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來,以便在需要的時(shí)候使用(如恢復(fù)到某一歷史狀態(tài)),可輔助實(shí)現(xiàn)撤銷操作。
原型模式的主要缺點(diǎn)如下:
- 需要為每一個(gè)類配備一個(gè)克隆方法,而且該克隆方法位于一個(gè)類的內(nèi)部,當(dāng)對(duì)已有的類進(jìn)行改造時(shí),需要修改源代碼,違背了“開閉原則”。
- 在實(shí)現(xiàn)深克隆時(shí)需要編寫較為復(fù)雜的代碼,而且當(dāng)對(duì)象之間存在多重的嵌套引用時(shí),為了實(shí)現(xiàn)深克隆,每一層對(duì)象對(duì)應(yīng)的類都必須支持深克隆,實(shí)現(xiàn)起來可能會(huì)比較麻煩。
適用場景
- 創(chuàng)建新對(duì)象成本較大(如初始化需要占用較長的時(shí)間,占用太多的CPU資源或網(wǎng)絡(luò)資源),新的對(duì)象可以通過原型模式對(duì)已有對(duì)象進(jìn)行復(fù)制來獲得,如果是相似對(duì)象,則可以對(duì)其成員變量稍作修改。
- 如果系統(tǒng)要保存對(duì)象的狀態(tài),而對(duì)象的狀態(tài)變化很小,或者對(duì)象本身占用內(nèi)存較少時(shí),可以使用原型模式配合備忘錄模式來實(shí)現(xiàn)。
- 需要避免使用分層次的工廠類來創(chuàng)建分層次的對(duì)象,并且類的實(shí)例對(duì)象只有一個(gè)或很少的幾個(gè)組合狀態(tài),通過復(fù)制原型對(duì)象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便。
原型模式在Spring中的應(yīng)用場景
在Spring中,用戶也可以采用原型模式來創(chuàng)建新的Bean實(shí)例,從而實(shí)現(xiàn)每次獲取的是通過克隆生成的新實(shí)例,對(duì)其進(jìn)行修改時(shí)對(duì)原有實(shí)例對(duì)象不造成任何影響。
這里的原型模式,也就是常說的Spring中的多實(shí)例模式,Spring中還有大家熟知的單實(shí)例模式,即Sigleton
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java中文件創(chuàng)建于寫入內(nèi)容的常見方法
在日常開發(fā)中,肯定離不開要和文件打交道,今天就簡單羅列一下平時(shí)比較常用的創(chuàng)建文件并向文件中寫入數(shù)據(jù)的幾種方式,希望對(duì)大家有一定的幫助2023-10-10Springboot中的@ComponentScan注解使用解析
這篇文章主要介紹了Springboot中的@ComponentScan注解使用解析,@ComponentScan用于類或接口上主要是指定掃描路徑,spring會(huì)把指定路徑下帶有指定注解的類注冊到IOC容器中,需要的朋友可以參考下2024-01-01SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié)
本文主要介紹了SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié),分別是InMemoryTokenStore,JdbcTokenStore,JwkTokenStore,RedisTokenStore,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01SpringBoot Entity中枚舉類型詳細(xì)使用介紹
本文介紹SpringBoot如何在Entity(DAO)中使用枚舉類型。(本文使用MyBatis-Plus)。在實(shí)際開發(fā)中,經(jīng)常會(huì)遇到表示類型或者狀態(tài)的情況,比如:有三種支付方式:微信、支付寶、銀聯(lián)。本文介紹如何這種場景的方案對(duì)比,并用實(shí)例來介紹如何用枚舉這種最優(yōu)雅的來表示2022-10-10es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程詳解
這篇文章主要介紹了es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06java中ArrayList與LinkedList對(duì)比詳情
這篇文章主要通過實(shí)例對(duì)Java中ArrayList與LinkedList進(jìn)行了對(duì)比,需要的朋友可以參考下2017-04-04