摘要:反射攻擊首先我們來(lái)看一下反射調(diào)用,以雙重檢驗(yàn)方式為例反射攻擊輸出結(jié)果是反射攻擊結(jié)果私有構(gòu)造方法被調(diào)用次私有構(gòu)造方法被調(diào)用次從結(jié)果可以看到,私有的構(gòu)造函數(shù)被調(diào)用了兩次,也就是說(shuō)這樣的單例模式并不安全。
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。
——艾迪生維斯理 《設(shè)計(jì)模式》
版權(quán)聲明:本文為 冬夏 原創(chuàng)發(fā)表在公眾號(hào)「Android從入門到精通」,可以隨意轉(zhuǎn)載,但請(qǐng)注明出處。概述
在我們?nèi)粘>帉懗绦虻臅r(shí)候,經(jīng)常需要一種這樣的對(duì)象。我們希望整個(gè)系統(tǒng)只有一個(gè)這樣的對(duì)象,不論在什么時(shí)候和不論在哪里獲取這個(gè)對(duì)象的時(shí)候,獲得的都是同一個(gè)對(duì)象。
比如說(shuō)系統(tǒng)的任務(wù)管理器,我們希望整個(gè)系統(tǒng)只有一個(gè)任務(wù)管理器,不論什么時(shí)候打開任務(wù)管理器,都可以看到當(dāng)前系統(tǒng)的所有任務(wù),而不是把任務(wù)分散在很多個(gè)任務(wù)管理器里。
又比如說(shuō)打印機(jī),當(dāng)電腦連接上一臺(tái)打印機(jī)的時(shí)候,我們會(huì)希望不管是在文檔A里使用或者在文檔B里使用的時(shí)候,都是同一臺(tái)打印機(jī),而且能夠按順序打印。
我們把這種類似的需求不斷總結(jié)并歸納起來(lái),就成了單例模式。
單例模式可以說(shuō)是所有設(shè)計(jì)模式里面最簡(jiǎn)單的了,但是要靈活并且準(zhǔn)確地使用它也不是那么容易的。
首先觀察一下單例模式的 UML 圖。
從 UML 圖中我們可以觀察到單例模式的幾個(gè)特點(diǎn)
私有的、靜態(tài)的實(shí)例對(duì)象
私有的構(gòu)造函數(shù)
公有的、靜態(tài)的獲取實(shí)例對(duì)象的方法
那么,什么樣的代碼可以同時(shí)滿足這幾個(gè)特點(diǎn)呢?
懶漢模式所謂的懶漢模式,就是一開始并不實(shí)例化對(duì)象,等到需要使用的時(shí)候才實(shí)例化。
{% codeblock 懶漢模式 lang:java %} public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } {% endcodeblock %}
從上面的代碼我們可以看到,當(dāng)?shù)谝淮潍@取 Singleton 實(shí)例的時(shí)候,instance 為空,將創(chuàng)建 Singleton 對(duì)象,并賦值給 instance 變量。以后的每次一獲取都將獲得第一次創(chuàng)建的 Singleton 對(duì)象,從而實(shí)現(xiàn)了唯一性。
線程安全驗(yàn)證仔細(xì)想想這段代碼,可能存在什么問(wèn)題呢?
假設(shè)有這么一種情況, Singleton 對(duì)象還沒(méi)有創(chuàng)建,這時(shí)候有很多個(gè)線程同時(shí)獲取 Singleton 對(duì)象,這時(shí)候會(huì)發(fā)生什么呢?
用下面的代碼可以驗(yàn)證
{% codeblock 懶漢模式 線程安全驗(yàn)證 lang:java %} public class Singleton { private static int count = 0; private static Singleton instance = null; private Singleton(){ try { Thread.sleep(10); }catch (InterruptedException e){ } System.out.println("Singleton 私有構(gòu)造方法被調(diào)用 " + ++count + "次"); } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } public class Test { public static void main(String[] args){ Runnable runnable = new Runnable() { @Override public void run() { Singleton singleton = Singleton.getInstance(); System.out.println("當(dāng)前線程:" + Thread.currentThread().getName() + " Singleton: " + singleton.hashCode()); } }; for (int i = 0; i < 10; i++){ new Thread(runnable).start(); } } } {% endcodeblock %}
從上面的代碼可以看到,我們對(duì)懶漢模式做了一點(diǎn)小修正,在創(chuàng)建 Singleton 對(duì)象的時(shí)候讓當(dāng)前線程休眠了10ms,這主要是因?yàn)橛?jì)算機(jī)運(yùn)算速度太快了,不讓當(dāng)前線程休眠一下的話很難出現(xiàn)想要的結(jié)果。關(guān)于休眠我們可以把它想象成創(chuàng)建對(duì)象的過(guò)程中需要消耗一定的時(shí)間。
運(yùn)算部分結(jié)果如下:
{% codeblock 懶漢模式 線程安全驗(yàn)證結(jié)果 lang:java %} Singleton 私有構(gòu)造方法被調(diào)用 1次 當(dāng)前線程:Thread-1 Singleton: 2044439889 Singleton 私有構(gòu)造方法被調(diào)用 4次 Singleton 私有構(gòu)造方法被調(diào)用 3次 Singleton 私有構(gòu)造方法被調(diào)用 2次 當(dāng)前線程:Thread-0 Singleton: 605315508 當(dāng)前線程:Thread-2 Singleton: 2298428 當(dāng)前線程:Thread-3 Singleton: 1005746524 當(dāng)前線程:Thread-4 Singleton: 1005746524 當(dāng)前線程:Thread-5 Singleton: 1005746524 當(dāng)前線程:Thread-6 Singleton: 1005746524 當(dāng)前線程:Thread-7 Singleton: 1005746524 當(dāng)前線程:Thread-8 Singleton: 1005746524 當(dāng)前線程:Thread-9 Singleton: 1005746524 {% endcodeblock %}
從上面的結(jié)果可以看到,Singleton 的私有構(gòu)造方法被調(diào)用了不止一次。對(duì)此的解釋是,當(dāng)?shù)谝淮潍@取 Singleton 對(duì)象還沒(méi)完成的時(shí)候,線程被系統(tǒng)掛起了,這時(shí)候有其他線程剛好也獲取了 Singleton 對(duì)象,那么就會(huì)產(chǎn)生多個(gè) Singleton 對(duì)象。
由此我們可以得出結(jié)論:懶漢模式是 非線程安全 的。
同步方法為了解決懶漢模式非線程安全的缺點(diǎn),就出現(xiàn)了改進(jìn)的懶漢模式。其原理是當(dāng)多個(gè)線程同時(shí)獲取 Singleton 對(duì)象時(shí),一次只讓一個(gè)線程獲取,其他線程都在等待,這樣就解決了多線程下的對(duì)象獲取問(wèn)題。
{% codeblock 同步方法 lang:java %} public class Singleton { private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } {% endcodeblock %}
我們通過(guò) synchronized 關(guān)鍵字讓 getInstance()方法一次只能讓一個(gè)線程調(diào)用,但是隨著而來(lái)的又有另外一個(gè)問(wèn)題。
那就是 效率問(wèn)題,因?yàn)橹挥械谝淮潍@取 Singleton 對(duì)象時(shí)有可能發(fā)生線程安全問(wèn)題,但是使用同步方法卻讓每次只讓一個(gè)線程能訪問(wèn)getInstance()方法,而不管 Singleton 對(duì)象是不是已經(jīng)被創(chuàng)建出來(lái)了。
那么有沒(méi)有辦法能同時(shí)解決線程安全和效率問(wèn)題呢?
雙重校驗(yàn)雙重校驗(yàn) 方式就是為了解決懶漢模式的線程安全和效率問(wèn)題而產(chǎn)生的。
{% codeblock 雙重校驗(yàn) lang:java %} public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } } {% endcodeblock %}
雙重校驗(yàn)就是將前面兩種懶漢模式結(jié)合起來(lái)。當(dāng)?shù)谝淮潍@取 Singleton 對(duì)象時(shí), instance 為空, 這時(shí)候?yàn)榱私鉀Q可能存在的線程安全問(wèn)題,同步了 Singleton 這個(gè)類對(duì)象。也就是說(shuō),同一時(shí)刻只能有一個(gè)線程能夠執(zhí)行 synchronized 之后的代碼。同時(shí)因?yàn)橥酱a外層有一個(gè)條件語(yǔ)句,所以同步代碼只有在第一次獲取 Singleton 對(duì)象的時(shí)候執(zhí)行到,這樣就解決了效率問(wèn)題。
但是這種方法還是有一個(gè)問(wèn)題,那就是 instance = new Singleton() 這一行代碼并不是原子性的
具體來(lái)說(shuō),JVM執(zhí)行這一行代碼時(shí)主要做了三件事
給 instance 分配內(nèi)存空間
調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量
將 instance 變量指向分配的內(nèi)存空間(執(zhí)行完這一步之后 instance 就不為 null 了)
由于 JVM 的指令優(yōu)化存在,上面的第二點(diǎn)和第三點(diǎn)并不能保證一定按順序執(zhí)行。也就是說(shuō)執(zhí)行順序有可能為 1-2-3 或者 1-3-2。
假設(shè)是 1-3-2,那么如果執(zhí)行到3的時(shí)候,線程被搶占了,有另外一個(gè)線程獲取了單例對(duì)象(這時(shí)候 instance 不為 null,但是還沒(méi)有初始化),那么自然就會(huì)出現(xiàn)錯(cuò)誤。
為了解決這個(gè)問(wèn)題,我們只要將 instance 變量聲明成 volatile 就可以了。
private static volatile Singleton instance = null;
volatile 關(guān)鍵字主要有兩個(gè)特性
可見性:保證線程沒(méi)有變量的本地副本,每次都去主內(nèi)存獲取最新版本
禁止指令重排序:生成內(nèi)存屏障
很明顯,我們這里利用的是 volatile 的第二個(gè)特性。
特別注意的是只有在 Java 5 之后使用這種方式才是完全安全的,原因是 Java 5 之前的 Java 內(nèi)存模型(Java Memory Model,JMM)存在缺陷,即使變量聲明為 volatile 也不能完全避免重排序,這個(gè)問(wèn)題在 Java 5 之后才修復(fù)。
惡漢模式這時(shí)候我們可以換個(gè)思路,既然懶漢模式是因?yàn)樾枰臅r(shí)候才創(chuàng)建對(duì)象,所以才讓程序有機(jī)會(huì)可以產(chǎn)生多個(gè)對(duì)象。那如果我一開始就把對(duì)象創(chuàng)建好了,不就行了嗎?這就出現(xiàn)了惡漢模式。
惡漢模式的意思是不管對(duì)象目前有沒(méi)有使用,都會(huì)先創(chuàng)建出來(lái)。
{% codeblock 惡漢模式 lang:java %} public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } } {% endcodeblock %}
從代碼中可以看到,由于在 Singleton 類加載時(shí)就創(chuàng)建了 Singleton 對(duì)象,所以惡漢模式是 線程安全 的。
但是惡漢模式存在的問(wèn)題就是不管目前對(duì)象有沒(méi)有被使用,都被創(chuàng)建了出來(lái),浪費(fèi)了內(nèi)存空間。
靜態(tài)方法靜態(tài)方法的單例模式和惡漢模式的原理一樣,都是利用了classloader,在類加載的時(shí)候就創(chuàng)建了 Singleton 對(duì)象。
{% codeblock 靜態(tài)方法 lang:java %} public class Singleton { private static Singleton instance = null; static { instance = new Singleton(); } private Singleton(){} public static Singleton getInstance() { return instance; } } {% endcodeblock %}靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類的方法和上面兩種方法既有相似的地方,也有不同的地方。
{% codeblock 靜態(tài)內(nèi)部類 lang:java %} public class Singleton { private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } private Singleton(){} public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } {% endcodeblock %}
從代碼種我們可以看到,靜態(tài)內(nèi)部類的方法和前兩種方法一樣,都是利用了classloader,在加載類的時(shí)候創(chuàng)建 Singleton 對(duì)象。
不同的地方在于加載的類不同。靜態(tài)內(nèi)部類方法在加載 Singleton 類的時(shí)候不會(huì)創(chuàng)建 Singleton 對(duì)象。而是在加載 SingletonHolder 類的時(shí)候才會(huì)。那么 SingletonHolder 類是什么時(shí)候加載的呢?
根據(jù)JVM(Java 虛擬機(jī))的類加載規(guī)則,靜態(tài)內(nèi)部類只有在主動(dòng)調(diào)用的時(shí)候才會(huì)加載。也就是說(shuō),在第一次調(diào)用 getInstance() 方法時(shí)才會(huì)加載 SingletonHolder 類,同時(shí)創(chuàng)建了 Singleton 對(duì)象。
也可以說(shuō),靜態(tài)內(nèi)部類的方法利用JVM解決了前兩種方法占用內(nèi)存的問(wèn)題。
防止單例受到攻擊到目前為止,我們所分析的所有單例模式都有一個(gè)前提,那就是調(diào)用者非常聽話地使用了 Singleton.getInstance() 方法獲取單例對(duì)象。但是在現(xiàn)實(shí)生活中是不是都是這樣的呢?會(huì)不會(huì)有不懷好意的人使用其他方式破壞我們的單例模式呢?
我們先思考一下,獲取一個(gè)對(duì)象有幾種方式
使用 new 關(guān)鍵字
通過(guò)反射調(diào)用
序列化
我們前面的單例模式都是通過(guò)第一種方式獲取對(duì)象的,那么如果采用其他兩種方式,之前的單例模式還安全嗎?答案是否定的。
反射攻擊首先我們來(lái)看一下反射調(diào)用,以雙重檢驗(yàn)方式為例
{% codeblock 反射攻擊 lang:java %} public class Singleton { private static volatile Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } } public class Test { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{ Singleton singleton1 = Singleton.getInstance(); Class> classType = Singleton.class; Constructor> constructor = classType.getDeclaredConstructor(null); constructor.setAccessible(true); Singleton singleton2 = (Singleton) constructor.newInstance(); System.out.println(singleton1 == singleton2); //false } } {% endcodeblock %}
輸出結(jié)果是
{% codeblock 反射攻擊結(jié)果 lang:java %} Singleton 私有構(gòu)造方法被調(diào)用 1次 Singleton 私有構(gòu)造方法被調(diào)用 2次 false {% endcodeblock %}
從結(jié)果可以看到,私有的構(gòu)造函數(shù)被調(diào)用了兩次,也就是說(shuō)這樣的單例模式并不安全。
為了防止單例模式被反射攻擊,我們可以添加一個(gè)標(biāo)志位,在新建對(duì)象時(shí)判斷是否已經(jīng)新建過(guò)對(duì)象了。
{% codeblock 防止反射攻擊 lang:java %} public class Singleton { private static boolean flag = false; private static volatile Singleton instance = null; private Singleton(){ if (!flag){ flag = true; }else { throw new RuntimeException("構(gòu)造函數(shù)被調(diào)用多次"); } } public static Singleton getInstance() { if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } } {% endcodeblock %}
當(dāng)然這種方式也有一個(gè)缺點(diǎn),那就是必須保證 Singleton.getInstance() 方法在反射之前調(diào)用,否則將不能正確獲取單例對(duì)象。
而且,既然我們可以通過(guò)反射創(chuàng)建出對(duì)象,那么也可以通過(guò)反射修改標(biāo)志位的值,這樣一來(lái),使用標(biāo)志位的方法就不能完全防止反射攻擊了。
序列化攻擊接下來(lái)我們看一下序列化如何破壞單例模式,以惡漢模式為例。
{% codeblock 序列化攻擊 lang:java %} public class Singleton implements Serializable{ private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } } public class Test { public static void main(String[] args) throws IOException,ClassNotFoundException{ Singleton singleton1 = Singleton.getInstance(); Singleton singleton2; FileOutputStream fos = new FileOutputStream("SerSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton1); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SerSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); singleton2 = (Singleton)ois.readObject(); System.out.println(singleton1==singleton2); } } {% endcodeblock %}
輸出結(jié)果為 false 表明我們的單例收到了攻擊,那么如何防止這種情況呢?
我們可以在被序列化的類中添加readResolve方法
{% codeblock 防止序列化攻擊 lang:java %} public class Singleton implements Serializable{ private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } private Object readResolve(){ return instance; } } {% endcodeblock %}
說(shuō)了這么多,不知道大家有沒(méi)有這樣一種感慨 「 都說(shuō)單例模式是最簡(jiǎn)單的一種模式,這么還這么復(fù)雜,以后還讓不讓人活了 」。
那么有沒(méi)有一種又簡(jiǎn)單有能防止所有攻擊的方法呢?
枚舉枚舉( enum )是 Java1.5 之后新加的特性。
大家一定很奇怪,為什么枚舉可以實(shí)現(xiàn)單例呢?其實(shí)和 Java 的編譯特性有關(guān)。因?yàn)槊杜e是 Java1.5 之后新加的,一般新加入的功能有一個(gè)很重要的問(wèn)題需要解決,就是對(duì)以前代碼的兼容性問(wèn)題。而 Java 是通過(guò) 語(yǔ)法糖 的方式解決的。簡(jiǎn)單來(lái)說(shuō)就是編寫代碼的時(shí)候可以使用新的關(guān)鍵字 enum 編寫程序,但是 Java 編譯器在編譯成字節(jié)碼的時(shí)候,還是會(huì)利用現(xiàn)有的技術(shù)編譯成之前的 JVM 能夠識(shí)別并正確運(yùn)行的字節(jié)碼,這就是語(yǔ)法糖技術(shù)。
我們先來(lái)看一下枚舉編寫的單例是什么樣子的。
{% codeblock 枚舉 lang:java %} public enum Singleton { INSTANCE; public static Singleton getInstance(){ return INSTANCE; } public void otherMethods(){ System.out.println("do something"); } } {% endcodeblock %}
這段代碼看起來(lái)很簡(jiǎn)單,我們定義了一個(gè)枚舉類型 INSTANCE, 這就是我們需要的單例。但是為什么這樣就能實(shí)現(xiàn)線程安全的單例呢?要解決這個(gè)疑問(wèn),我們必須把這段代碼進(jìn)行反編譯,看看 java 編譯器究竟是如何編譯這段代碼的。
我們使用 java 自帶的反編譯工具 javap 就可以將這段代碼反編譯
javap -c Singleton
反編譯結(jié)果如下:
{% codeblock 反編譯 lang:java %} public final class Singleton extends java.lang.Enum{ public static final Singleton INSTANCE; public static Singleton[] values(); Code: 0: getstatic #1 // Field $VALUES:[LSingleton; 3: invokevirtual #2 // Method "[LSingleton;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LSingleton;" 9: areturn public static Singleton valueOf(java.lang.String); Code: 0: ldc #4 // class Singleton 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class Singleton 9: areturn public static Singleton getInstance(); Code: 0: getstatic #7 // Field INSTANCE:LSingleton; 3: areturn public void otherMethods(); Code: 0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #9 // String do something 5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return static {}; Code: 0: new #4 // class Singleton 3: dup 4: ldc #11 // String INSTANCE 6: iconst_0 7: invokespecial #12 // Method " ":(Ljava/lang/String;I)V 10: putstatic #7 // Field INSTANCE:LSingleton; 13: iconst_1 14: anewarray #4 // class Singleton 17: dup 18: iconst_0 19: getstatic #7 // Field INSTANCE:LSingleton; 22: aastore 23: putstatic #1 // Field $VALUES:[LSingleton; 26: return } {% endcodeblock %}
可能這段代碼對(duì)于剛剛接觸 java 的人來(lái)說(shuō)一時(shí)可能看不懂,但是我們只要關(guān)注到一下幾點(diǎn)就好了。
public final class Singleton extends java.lang.Enum
public static final Singleton INSTANCE,說(shuō)明我們定義的枚舉值 INSTANCE 實(shí)際上被 java 編譯器轉(zhuǎn)換成了不可變對(duì)象,只可以初始化一次。
關(guān)注到 INSTANCE 實(shí)際上是在 static {} 這段代碼里初始化的。也就是說(shuō), INSTANCE 是在 Singleton 類加載的時(shí)候初始化的,所以一旦 Singleton 類加載了,INSTANCE 也就初始化了,不能再改變了,這就實(shí)現(xiàn)了單例模式。
然后如果我們嘗試使用序列化或者反射的方式去攻擊枚舉單例,會(huì)發(fā)現(xiàn)都不能成功,這是由于 JVM 實(shí)現(xiàn)枚舉的機(jī)制決定的。
最后,引用一下 《Effective Java》一書中的話。
單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。
——《Effective Java》
歡迎關(guān)注我的個(gè)人公眾號(hào),一起學(xué)習(xí)Android、Java、設(shè)計(jì)模式等技術(shù)!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/68109.html
摘要:用來(lái)指向已創(chuàng)建好的實(shí)例構(gòu)造函數(shù)為空注意這里是關(guān)鍵這是我們需要調(diào)用的方法把函數(shù)也定義為空,這樣就大功告成啦。 接上一篇大話PHP設(shè)計(jì)模式之單例模式 這一篇介紹一下升級(jí)版的單例模式,廢話不說(shuō)先上代碼 不完美的單例模式 class singleMode { //用來(lái)指向已創(chuàng)建好的實(shí)例 public static $instance; //判斷是...
摘要:博主按每天一個(gè)設(shè)計(jì)模式旨在初步領(lǐng)會(huì)設(shè)計(jì)模式的精髓,目前采用靠這吃飯和純粹喜歡兩種語(yǔ)言實(shí)現(xiàn)。單例模式用途如果一個(gè)類負(fù)責(zé)連接數(shù)據(jù)庫(kù)的線程池日志記錄邏輯等等,此時(shí)需要單例模式來(lái)保證對(duì)象不被重復(fù)創(chuàng)建,以達(dá)到降低開銷的目的。 博主按:《每天一個(gè)設(shè)計(jì)模式》旨在初步領(lǐng)會(huì)設(shè)計(jì)模式的精髓,目前采用javascript(_靠這吃飯_)和python(_純粹喜歡_)兩種語(yǔ)言實(shí)現(xiàn)。誠(chéng)然,每種設(shè)計(jì)模式都有多種實(shí)...
摘要:博主按每天一個(gè)設(shè)計(jì)模式旨在初步領(lǐng)會(huì)設(shè)計(jì)模式的精髓,目前采用靠這吃飯和純粹喜歡兩種語(yǔ)言實(shí)現(xiàn)。單例模式用途如果一個(gè)類負(fù)責(zé)連接數(shù)據(jù)庫(kù)的線程池日志記錄邏輯等等,此時(shí)需要單例模式來(lái)保證對(duì)象不被重復(fù)創(chuàng)建,以達(dá)到降低開銷的目的。 博主按:《每天一個(gè)設(shè)計(jì)模式》旨在初步領(lǐng)會(huì)設(shè)計(jì)模式的精髓,目前采用javascript(_靠這吃飯_)和python(_純粹喜歡_)兩種語(yǔ)言實(shí)現(xiàn)。誠(chéng)然,每種設(shè)計(jì)模式都有多種實(shí)...
摘要:上面是簡(jiǎn)單的單例模式,自己寫程序的話夠用了,如果想繼續(xù)延伸,請(qǐng)傳送至大話設(shè)計(jì)模式之單例模式升級(jí)版 看了那么多單例的介紹,都是上來(lái)就說(shuō)怎么做,也沒(méi)見說(shuō)為什么這么做的。那小的就來(lái)說(shuō)說(shuō)為什么會(huì)有單例這個(gè)模式以便更好的幫助初學(xué)者真正的理解這個(gè)設(shè)計(jì)模式,如果你是大神,也不妨看完指正一下O(∩_∩)O首先我不得不吐槽一下這個(gè)模式名字單例,初學(xué)者通過(guò)字面很難理解什么是單例,我覺(jué)得應(yīng)該叫唯一模式更貼切...
摘要:最近開展了三次設(shè)計(jì)模式的公開課,現(xiàn)在來(lái)總結(jié)一下設(shè)計(jì)模式在中的應(yīng)用,這是第一篇?jiǎng)?chuàng)建型模式之單例模式。不過(guò)因?yàn)椴恢С侄嗑€程所以不需要考慮這個(gè)問(wèn)題了。 最近開展了三次設(shè)計(jì)模式的公開課,現(xiàn)在來(lái)總結(jié)一下設(shè)計(jì)模式在PHP中的應(yīng)用,這是第一篇?jiǎng)?chuàng)建型模式之單例模式。 一、設(shè)計(jì)模式簡(jiǎn)介 首先我們來(lái)認(rèn)識(shí)一下什么是設(shè)計(jì)模式: 設(shè)計(jì)模式是一套被反復(fù)使用、容易被他人理解的、可靠的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。 設(shè)計(jì)模式不...
摘要:原文博客地址單例模式系統(tǒng)中被唯一使用,一個(gè)類只有一個(gè)實(shí)例。中的單例模式利用閉包實(shí)現(xiàn)了私有變量?jī)烧呤欠裣嗟热躅愋停瑳](méi)有私有方法,使用者還是可以直接一個(gè),也會(huì)有方法分割線不是單例最簡(jiǎn)單的單例模式,就是對(duì)象。 原文博客地址:https://finget.github.io/2018/11/06/single/ 單例模式 系統(tǒng)中被唯一使用,一個(gè)類只有一個(gè)實(shí)例。實(shí)現(xiàn)方法一般是先判斷實(shí)例是否存在,...
閱讀 848·2021-11-18 10:07
閱讀 2359·2021-10-14 09:42
閱讀 5347·2021-09-22 15:45
閱讀 593·2021-09-03 10:29
閱讀 3471·2021-08-31 14:28
閱讀 1881·2019-08-30 15:56
閱讀 3046·2019-08-30 15:54
閱讀 1001·2019-08-29 11:32