Java設(shè)計(jì)模式之單例和原型
今天這篇文章我們來學(xué)習(xí)創(chuàng)建型設(shè)計(jì)模式的另外兩個(gè)孿生兄弟,單例和原型,其中原型設(shè)計(jì)模式中我們深入到JVM的內(nèi)存模型,最后順便談?wù)凧ava中的值傳遞和引用傳遞。
上篇文章老王買產(chǎn)品 我們從最原始的基本實(shí)現(xiàn)方法,到簡單(靜態(tài))工廠,然后使用工廠方法設(shè)計(jì)模式進(jìn)行改造,最后考慮產(chǎn)品會(huì)產(chǎn)生變體,我們又?jǐn)U展到了抽象工廠。
設(shè)計(jì)模式所有的相關(guān)代碼均已上傳到碼云 讀者可以自行下載學(xué)習(xí)測(cè)試,本地源碼下載。
一、引出問題
今天老王又來了,還是想買我們的產(chǎn)品,今天老王上老就提出來一個(gè)要求,當(dāng)他購買產(chǎn)品的時(shí)候,每次都要從貨架上給他拿相同的一個(gè)。
如果用傳統(tǒng)實(shí)現(xiàn)方式,當(dāng)老王拿到產(chǎn)品以后,直接和上一個(gè)比對(duì)一下就行了,如果不一致老王就還回來。
但通過我們查閱軟件的七大設(shè)計(jì)原則 ,這很明顯違反了依賴倒置原則,為了避免耦合和讓代碼更易于維護(hù),老王是不能依賴具體產(chǎn)品的。
二、單例
我們就需要將產(chǎn)品比對(duì)在創(chuàng)建產(chǎn)品的時(shí)候進(jìn)行判斷,老王就只管拿。
老王來之前應(yīng)該還有兩種情況,一種就是老王還沒來,產(chǎn)品就準(zhǔn)備好了,也即餓漢式。第二種就是老王什么時(shí)候來,什么時(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;
}
}老王類:
/**
* @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ì)模式中最簡單的一個(gè),理解起來難度也不大。
為了克服老王和他兒子小王一起來拿錯(cuò)的尷尬,我們?cè)诜椒ㄉ霞觭ynchronized鎖,對(duì)象引用上加volatile共享變量,但這樣會(huì)帶來效率問題,如果不考慮多線程需求,讀者可自行去掉。
三、原型
老王今天很明顯是找茬,他繼續(xù)說,如果我不想要一個(gè)了,我要每次買都要不同的,你看著辦。
每次創(chuàng)建產(chǎn)品都要不同的,傳統(tǒng)的方式肯定就是重新new一個(gè)對(duì)象,但每創(chuàng)建一個(gè)對(duì)象都是一個(gè)復(fù)雜的過程,而且這樣還會(huì)帶來一定的代碼冗余。
這就需要用到創(chuàng)建型設(shè)計(jì)模式中的原型模式中的拷貝,其中又分為淺拷貝和深拷貝。
我們先看基本概念。
- 淺克?。簞?chuàng)建一個(gè)新對(duì)象,對(duì)象種屬性和原來對(duì)象的屬性完全相同,對(duì)于非基本類型屬性仍指向原有屬性所指向的內(nèi)存地址
- 深克?。簞?chuàng)建一個(gè)新對(duì)象,屬性中引用類型也會(huì)被克隆,不再指向原來屬性所指向的內(nèi)存地址
這段意思也就是,老王購買產(chǎn)品的時(shí)候,如果產(chǎn)品都是基本數(shù)據(jù)類型(byte(位)、short(短整數(shù))、int(整數(shù))、long(長整數(shù))、float(單精度)、double(雙精度)、char(字符)和boolean(布爾值))和String,那么我們就使用淺拷貝。
如果產(chǎn)品包括別的產(chǎn)品(對(duì)象)的引用類型就要使用深拷貝。
如果想搞明白,為什么造成深拷貝和淺拷貝這個(gè)問題,我們就要重點(diǎn)說說JVM的內(nèi)存模型。
我們聲明一個(gè)基本數(shù)據(jù)類型的變量a=2,實(shí)際上是在棧中直接存儲(chǔ)了一個(gè)a=2,當(dāng)拷貝的時(shí)候直接把值拷貝過去,也就是直接有了一份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è)賮砜碨tring類型。String 存在于堆內(nèi)存、常量池;這種比較特殊, 傳遞是引用地址;由本身的final性, 每次賦值都是一個(gè)新的引用地址,原對(duì)象的引用和副本的引用互不影響。因此String就和基本數(shù)據(jù)類型一樣,表現(xiàn)出了"深拷貝"特性。
我們具體看實(shí)現(xiàn)代碼:
淺拷貝類:
/**
* @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接口,并重寫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ì)象中包括引用類型呢?那怎么實(shí)現(xiàn)呢。
其實(shí)原理上也是很簡單的,只需要將非基本數(shù)據(jù)類型也像淺拷貝那樣操做就行了,然后在當(dāng)前clone()方法中,調(diào)用非基本數(shù)據(jù)類型的clone()方法
深拷貝引用類:
/**
* @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();
}
}深拷貝類:
/**
* @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();
}老李、老王都正確的輸出了,說明實(shí)現(xiàn)沒有問題。
這樣就符合了老王的要求。
既然說到了jvm的內(nèi)存模型,就有必要說一下java中的值傳遞和引用傳遞。
實(shí)際上java應(yīng)該就是值傳遞,在調(diào)用方法的時(shí)候,如果參數(shù)是基本數(shù)據(jù)類型,那么傳遞的就是副本,我們?cè)诜椒ㄖ袩o論怎么給他賦值,他原本的值都不會(huì)有變化。
在調(diào)用方法的時(shí)候,如果參數(shù)是引用數(shù)據(jù)類型,那么傳遞的就是這個(gè)對(duì)象的地址,我們?cè)诜椒ㄖ行薷倪@個(gè)對(duì)象都會(huì)影響他原本的對(duì)象。
造成這個(gè)現(xiàn)象的原因其實(shí)是和淺拷貝、深拷貝的原理是一樣的,都是棧、堆內(nèi)存的結(jié)構(gòu)導(dǎo)致的。
老王看他的要求都滿足了,最后心滿意足的拿著產(chǎn)品走了。
以上就是這篇文章的全部內(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ì)模式中的門面模式詳解
- Java結(jié)構(gòu)型設(shè)計(jì)模式之組合模式詳解
相關(guān)文章
Spring-Boot 集成Solr客戶端的詳細(xì)步驟
本篇文章主要介紹了Spring-Boot 集成Solr客戶端的詳細(xì)步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
JavaWeb入門:HttpResponse和HttpRequest詳解
這篇文章主要介紹了Django的HttpRequest和HttpResponse對(duì)象,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2021-07-07
關(guān)于@EnableGlobalMethodSecurity注解的用法解讀
這篇文章主要介紹了關(guān)于@EnableGlobalMethodSecurity注解的用法解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java文件處理之使用XWPFDocument導(dǎo)出Word文檔
最近因項(xiàng)目開發(fā)的需要,整理了一份用JAVA導(dǎo)出WORD文檔,下面這篇文章主要給大家介紹了關(guān)于Java文件處理之使用XWPFDocument導(dǎo)出Word文檔的相關(guān)資料,需要的朋友可以參考下2023-12-12
Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之平行志愿管理系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+Springboot+Maven+mybatis+Vue+Mysql開發(fā)的圖片平行志愿管理系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有志愿管理該有的所有功能,感興趣的朋友快來看看吧2022-02-02
使用SpringBoot的CommandLineRunner遇到的坑及解決
這篇文章主要介紹了使用SpringBoot的CommandLineRunner遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
MyBatis中調(diào)用存儲(chǔ)過程和函數(shù)的實(shí)現(xiàn)示例
在MyBatis中調(diào)用存儲(chǔ)過程和函數(shù)是一個(gè)相對(duì)高級(jí)的特性,本文主要介紹了MyBatis中調(diào)用存儲(chǔ)過程和函數(shù)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07

