Java設(shè)計(jì)模式之單例和原型
今天這篇文章我們來(lái)學(xué)習(xí)創(chuàng)建型設(shè)計(jì)模式的另外兩個(gè)孿生兄弟,單例和原型,其中原型設(shè)計(jì)模式中我們深入到JVM的內(nèi)存模型,最后順便談?wù)凧ava中的值傳遞和引用傳遞。
上篇文章老王買(mǎi)產(chǎn)品 我們從最原始的基本實(shí)現(xiàn)方法,到簡(jiǎn)單(靜態(tài))工廠,然后使用工廠方法設(shè)計(jì)模式進(jìn)行改造,最后考慮產(chǎn)品會(huì)產(chǎn)生變體,我們又?jǐn)U展到了抽象工廠。
設(shè)計(jì)模式所有的相關(guān)代碼均已上傳到碼云 讀者可以自行下載學(xué)習(xí)測(cè)試,本地源碼下載。
一、引出問(wèn)題
今天老王又來(lái)了,還是想買(mǎi)我們的產(chǎn)品,今天老王上老就提出來(lái)一個(gè)要求,當(dāng)他購(gòu)買(mǎi)產(chǎn)品的時(shí)候,每次都要從貨架上給他拿相同的一個(gè)。
如果用傳統(tǒng)實(shí)現(xiàn)方式,當(dāng)老王拿到產(chǎn)品以后,直接和上一個(gè)比對(duì)一下就行了,如果不一致老王就還回來(lái)。
但通過(guò)我們查閱軟件的七大設(shè)計(jì)原則 ,這很明顯違反了依賴(lài)倒置原則,為了避免耦合和讓代碼更易于維護(hù),老王是不能依賴(lài)具體產(chǎn)品的。
二、單例
我們就需要將產(chǎn)品比對(duì)在創(chuàng)建產(chǎn)品的時(shí)候進(jìn)行判斷,老王就只管拿。
老王來(lái)之前應(yīng)該還有兩種情況,一種就是老王還沒(méi)來(lái),產(chǎn)品就準(zhǔn)備好了,也即餓漢式。第二種就是老王什么時(shí)候來(lái),什么時(shí)候給他準(zhǔn)備產(chǎn)品,也即懶漢式。
我們看具體的實(shí)現(xiàn)代碼:
懶漢式:
/** * 懶漢式 * @author tcy * @Date 29-07-2022 */ public class LazySingletonProduct { private static volatile LazySingletonProduct instance=null; private LazySingletonProduct(){} public static synchronized LazySingletonProduct getInstance(){ if (instance==null){ instance=new LazySingletonProduct(); } return instance; }
餓漢式:
/** * 餓漢式 * @author tcy * @Date 29-07-2022 */ public class HungrySingletonProduct { private static volatile HungrySingletonProduct instance=new HungrySingletonProduct(); private HungrySingletonProduct(){}; public static synchronized HungrySingletonProduct getInstance(){ if (instance==null){ instance=new HungrySingletonProduct(); } return instance; } }
老王類(lèi):
/** * @author tcy * @Date 29-07-2022 */ public class Client { public static void main(String[] args) { HungrySingletonProduct instance1 = HungrySingletonProduct.getInstance(); HungrySingletonProduct instance2 = HungrySingletonProduct.getInstance(); if (instance1==instance2){ System.out.println("我倆一樣..."); }else { System.out.println("我倆不一樣..."); } } }
以上就是單例設(shè)計(jì)模式中的懶漢式和餓漢式,應(yīng)該是設(shè)計(jì)模式中最簡(jiǎn)單的一個(gè),理解起來(lái)難度也不大。
為了克服老王和他兒子小王一起來(lái)拿錯(cuò)的尷尬,我們?cè)诜椒ㄉ霞觭ynchronized鎖,對(duì)象引用上加volatile共享變量,但這樣會(huì)帶來(lái)效率問(wèn)題,如果不考慮多線(xiàn)程需求,讀者可自行去掉。
三、原型
老王今天很明顯是找茬,他繼續(xù)說(shuō),如果我不想要一個(gè)了,我要每次買(mǎi)都要不同的,你看著辦。
每次創(chuàng)建產(chǎn)品都要不同的,傳統(tǒng)的方式肯定就是重新new一個(gè)對(duì)象,但每創(chuàng)建一個(gè)對(duì)象都是一個(gè)復(fù)雜的過(guò)程,而且這樣還會(huì)帶來(lái)一定的代碼冗余。
這就需要用到創(chuàng)建型設(shè)計(jì)模式中的原型模式中的拷貝,其中又分為淺拷貝和深拷貝。
我們先看基本概念。
- 淺克?。簞?chuàng)建一個(gè)新對(duì)象,對(duì)象種屬性和原來(lái)對(duì)象的屬性完全相同,對(duì)于非基本類(lèi)型屬性仍指向原有屬性所指向的內(nèi)存地址
- 深克?。簞?chuàng)建一個(gè)新對(duì)象,屬性中引用類(lèi)型也會(huì)被克隆,不再指向原來(lái)屬性所指向的內(nèi)存地址
這段意思也就是,老王購(gòu)買(mǎi)產(chǎn)品的時(shí)候,如果產(chǎn)品都是基本數(shù)據(jù)類(lèi)型(byte(位)、short(短整數(shù))、int(整數(shù))、long(長(zhǎng)整數(shù))、float(單精度)、double(雙精度)、char(字符)和boolean(布爾值))和String,那么我們就使用淺拷貝。
如果產(chǎn)品包括別的產(chǎn)品(對(duì)象)的引用類(lèi)型就要使用深拷貝。
如果想搞明白,為什么造成深拷貝和淺拷貝這個(gè)問(wèn)題,我們就要重點(diǎn)說(shuō)說(shuō)JVM的內(nèi)存模型。
我們聲明一個(gè)基本數(shù)據(jù)類(lèi)型的變量a=2,實(shí)際上是在棧中直接存儲(chǔ)了一個(gè)a=2,當(dāng)拷貝的時(shí)候直接把值拷貝過(guò)去,也就是直接有了一份a的副本。
當(dāng)我們創(chuàng)建一個(gè)對(duì)象時(shí)Student stu=new Student(),實(shí)際上對(duì)象的值存儲(chǔ)在堆中,在棧中只存放了stu="對(duì)象地址",stu指向了堆中的地址,jvm拷貝的時(shí)候只復(fù)制了棧中的地址,實(shí)際上他們堆中的對(duì)象還是一個(gè)。
我們?cè)賮?lái)看String類(lèi)型。String 存在于堆內(nèi)存、常量池;這種比較特殊, 傳遞是引用地址;由本身的final性, 每次賦值都是一個(gè)新的引用地址,原對(duì)象的引用和副本的引用互不影響。因此String就和基本數(shù)據(jù)類(lèi)型一樣,表現(xiàn)出了"深拷貝"特性。
我們具體看實(shí)現(xiàn)代碼:
淺拷貝類(lèi):
/** * @author tcy * @Date 29-07-2022 */ public class ShallowProduct implements Cloneable{ private String name; private int num; public void show(){ System.out.println("這是淺產(chǎn)品..."+name+"數(shù)量:"+num); } public String getName() { return name; } public ShallowProduct setName(String name) { this.name = name; return this; } public int getNum() { return num; } public ShallowProduct setNum(int num) { this.num = num; return this; } @Override public ShallowProduct clone() throws CloneNotSupportedException { return (ShallowProduct) super.clone(); } }
如果需要哪個(gè)對(duì)象淺拷貝,需要該對(duì)象實(shí)現(xiàn)Cloneable接口,并重寫(xiě)clone()方法。
public void shallowTest()throws CloneNotSupportedException{ ShallowProduct product1=new ShallowProduct(); ShallowProduct product2 = product1.clone(); product1.setName("老王"); product2.setName("老李"); product1.setNum(1); product2.setNum(2); product1.show(); product2.show(); }
調(diào)用時(shí)輸出的對(duì)象中的值直接就是兩個(gè)不同的對(duì)象,實(shí)現(xiàn)了對(duì)象的淺拷貝。
如果該對(duì)象中包括引用類(lèi)型呢?那怎么實(shí)現(xiàn)呢。
其實(shí)原理上也是很簡(jiǎn)單的,只需要將非基本數(shù)據(jù)類(lèi)型也像淺拷貝那樣操做就行了,然后在當(dāng)前clone()方法中,調(diào)用非基本數(shù)據(jù)類(lèi)型的clone()方法
深拷貝引用類(lèi):
/** * @author tcy * @Date 29-07-2022 */ public class Child implements Cloneable{ private String childName; public String getChildName() { return childName; } public Child setChildName(String childName) { this.childName = childName; return this; } @Override protected Child clone() throws CloneNotSupportedException { return (Child) super.clone(); } }
深拷貝類(lèi):
/** * @author tcy * @Date 29-07-2022 */ public class DeepProduct implements Cloneable{ private String name; private Integer num; private Child child; public String getName() { return name; } public DeepProduct setName(String name) { this.name = name; return this; } public Integer getNum() { return num; } public DeepProduct setNum(Integer num) { this.num = num; return this; } public void show(){ System.out.println("這是深產(chǎn)品..."+name+"數(shù)量:"+num+"包括child:"+child.getChildName()); } @Override public DeepProduct clone() throws CloneNotSupportedException { DeepProduct clone = (DeepProduct) super.clone(); clone.child=child.clone(); return clone; } public Child getChild() { return child; } public DeepProduct setChild(Child child) { this.child = child; return this; } }
我們測(cè)試一下對(duì)象中的值是否發(fā)生了改變。
public void deepTest() throws CloneNotSupportedException { DeepProduct product1=new DeepProduct(); Child child=new Child(); child.setChildName("老王child"); product1.setName("老王"); product1.setNum(1); product1.setChild(child); //-------------- DeepProduct product2=product1.clone(); product2.setName("老李"); product2.setNum(2); product2.getChild().setChildName("老李child"); product1.show(); product2.show(); }
老李、老王都正確的輸出了,說(shuō)明實(shí)現(xiàn)沒(méi)有問(wèn)題。
這樣就符合了老王的要求。
既然說(shuō)到了jvm的內(nèi)存模型,就有必要說(shuō)一下java中的值傳遞和引用傳遞。
實(shí)際上java應(yīng)該就是值傳遞,在調(diào)用方法的時(shí)候,如果參數(shù)是基本數(shù)據(jù)類(lèi)型,那么傳遞的就是副本,我們?cè)诜椒ㄖ袩o(wú)論怎么給他賦值,他原本的值都不會(huì)有變化。
在調(diào)用方法的時(shí)候,如果參數(shù)是引用數(shù)據(jù)類(lèi)型,那么傳遞的就是這個(gè)對(duì)象的地址,我們?cè)诜椒ㄖ行薷倪@個(gè)對(duì)象都會(huì)影響他原本的對(duì)象。
造成這個(gè)現(xiàn)象的原因其實(shí)是和淺拷貝、深拷貝的原理是一樣的,都是棧、堆內(nèi)存的結(jié)構(gòu)導(dǎo)致的。
老王看他的要求都滿(mǎn)足了,最后心滿(mǎn)意足的拿著產(chǎn)品走了。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
- Java設(shè)計(jì)模式之工廠方法和抽象工廠
- Java設(shè)計(jì)模式之觀察者模式
- Java創(chuàng)建型設(shè)計(jì)模式之工廠方法模式深入詳解
- Java創(chuàng)建型設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)
- Java結(jié)構(gòu)型設(shè)計(jì)模式中建造者模式示例詳解
- Java結(jié)構(gòu)型設(shè)計(jì)模式之享元模式示例詳解
- Java結(jié)構(gòu)型設(shè)計(jì)模式中代理模式示例詳解
- Java設(shè)計(jì)模式中的門(mén)面模式詳解
- Java結(jié)構(gòu)型設(shè)計(jì)模式之組合模式詳解
相關(guān)文章
Gauva使用ListenableFuture介紹說(shuō)明
并發(fā)是一個(gè)困難問(wèn)題,但是通過(guò)強(qiáng)大和強(qiáng)大的抽象能夠顯著的簡(jiǎn)化工作。為了簡(jiǎn)化問(wèn)題,Gauva使用ListenableFuture擴(kuò)展了JDK的Future接口,這篇文章主要介紹了Gauva使用ListenableFuture2023-01-01Java接口的簡(jiǎn)單定義與實(shí)現(xiàn)方法示例
這篇文章主要介紹了Java接口的簡(jiǎn)單定義與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了java面向?qū)ο蟪绦蛟O(shè)計(jì)中接口的概念、功能、定義及使用技巧,需要的朋友可以參考下2019-01-01盤(pán)點(diǎn)總結(jié)SpringBoot自帶工具類(lèi)使用提升開(kāi)發(fā)效率
這篇文章主要為大家介紹了盤(pán)點(diǎn)總結(jié)SpringBoot自帶工具類(lèi)使用提升開(kāi)發(fā)效率,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12spring boot 加載web容器tomcat流程源碼分析
本文章主要描述spring boot加載web容器 tomcat的部分,為了避免文章知識(shí)點(diǎn)過(guò)于分散,其他相關(guān)的如bean的加載,tomcat內(nèi)部流程等不做深入討論,具體內(nèi)容詳情跟隨小編一起看看吧2021-06-06java hasNextInt判斷是否為數(shù)字的方法
今天小編就為大家分享一篇java hasNextInt判斷是否為數(shù)字的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Java中的數(shù)組流ByteArrayOutputStream用法
Java中的ByteArrayOutputStream是java.io包中的一個(gè)類(lèi),用于在內(nèi)存中創(chuàng)建字節(jié)數(shù)組緩沖區(qū),支持動(dòng)態(tài)擴(kuò)展,它繼承自O(shè)utputStream,允許以字節(jié)形式寫(xiě)入數(shù)據(jù),無(wú)需與外部設(shè)備交互,常用方法包括write()、toByteArray()、toString()等2024-09-09