天天看點

為什麼要用單例模式?

 我們在程式設計中最常用的模式就是單例模式了,然而單例模式都用在什麼場合?為什麼不用靜态方法而要用單例模式呢?要搞清這些問題,需要從靜态方法和非靜态方法的差別和聯系說起。

為什麼要用單例模式?

  一、靜态方法常駐記憶體,非靜态方法隻有使用的時候才配置設定記憶體?

  一般都認為是這樣,并且怕靜态方法占用過多記憶體而建議使用非靜态方法,其實這個了解是錯誤的。

  為什麼會這樣,先從記憶體配置設定開始說起:

  托管堆的定義:對于32位的應用程式來說,應用程式完成程序初始化後,clr将在程序的可用位址空間配置設定一塊保留的位址空間,它是程序(每個程序可使用4gb)中可用位址空間上的一塊記憶體區域,但并不對應任何實體記憶體,這塊位址空間即是托管堆。

  托管堆有分為多個區域,其中最重要的是垃圾回收堆(gc heap)和加載堆(loader heap),gc heap用于存儲對象執行個體,受gc管理;loader heap又分為high-frequency heap、low-frequency heap和stub heap,不同的堆上又存儲不同的資訊。loader heap最重要的資訊就是中繼資料相關的資訊,也就是type對象,每個type在loader heap上展現為一個method table(方法表),而method table中則記錄了存儲的中繼資料資訊,例如基類型、靜态字段、實作的接口、所有的方法等等。loader heap不受gc控制,其生命周期為從建立到appdomain解除安裝。(摘自《你必須知道的.net》)

  由此我們就明白了,靜态方法和非靜态方法,在記憶體裡其實都放在method table裡了,在一個類第一次被加載的時候,它會在loader heap裡把靜态方法,非靜态方法都寫入method table中,而且loader heap不受gc控制,是以一旦加載,gc就不會回收,直到appdomain解除安裝

  由此我們也明白了,靜态方法和非靜态方法,他們都是在第一次加載後就常駐記憶體,是以方法本身在記憶體裡,沒有什麼差別,是以也就不存在”靜态方法常駐記憶體,非靜态方法隻有使用的時候才配置設定記憶體“這個結論了。

  二、靜态方法和非靜态方法的差別?

  在記憶體中的差別是,非靜态方法在建立執行個體對象時,因為屬性的值對于每個對象都各不相同,是以在new一個執行個體時,會把這個執行個體屬性在gc heap裡拷貝一份,同時這個new出來的對象放在堆棧上,堆棧指針指向了剛才拷貝的那一份執行個體的記憶體位址上。而靜态方法則不需要,因為靜态方法裡面的靜态字段,就是儲存在method table裡了,隻有一份。

  是以靜态方法和非靜态方法,在調用速度上,靜态方法速度一定會快點,因為非靜态方法需要執行個體化,配置設定記憶體,但靜态方法不用,但是這種速度上差異可以忽略不計。

  三、為什麼要有非靜态方法?

  早期的結構化程式設計,幾乎所有的方法都是“靜态方法”,引入執行個體化方法概念是面向對象概念出現以後的事情了,區分靜态方法和執行個體化方法不能單單從性能上去了解,建立c++,java,c#這樣面向對象語言的大師引入執行個體化方法一定不是要解決什麼性能、記憶體的問題,而是為了讓開發更加模式化、面向對象化。這樣說的話,靜态方法和執行個體化方式的區分是為了解決模式的問題。

  接下來繼續思考,如果我們全部用靜态方法,不用非靜态方法,不是一樣能實作功能嗎?是的,沒錯,但是你的代碼是基于對象,而不是面向對象的,因為面向對象的繼承和多态,都是非靜态方法。

  第二個原因是為什麼不建議都用靜态方法,我們如果多線程的情況下,如果靜态方法使用了一個靜态字段,這個靜态字段可以會被多個線程修改,是以說如果在靜态方法裡使用了靜态變量,這就會有線程安全問題,當然了,就算不是多線程,因為靜态字段隻有一份,同樣會有被其他地方修改的問題。

  從這三點我們得出的結論如下:

  一、 什麼時候用靜态方法,什麼時候使用非靜态方法?

  既然靜态方法和執行個體化方式的區分是為了解決模式的問題,如果我們考慮不需要繼承和多态的時候,就可以使用靜态方法,但就算不考慮繼承和多态,就一概使用靜态方法也不是好的程式設計思想。

  從另一個角度考慮,如果一個方法和他所在類的執行個體對象無關,那麼它就應該是靜态的,否則就應該是非靜态。是以像工具類,一般都是靜态的。

  二、 為什麼使用單例模式而不用靜态方法?

  從面相對象的角度講:

  雖然都能實作目的,但是他們一個是基于對象,一個是面向對象的,就像我們不面相對象也能解決問題一樣,面相對象的代碼提供一個更好的程式設計思想。

  如果一個方法和他所在類的執行個體對象無關,那麼它就應該是靜态的,反之他就應該是非靜态的。如果我們确實應該使用非靜态的方法,但是在建立類時又确實隻需要維護一份執行個體時,就需要用單例模式了。

  比如說我們在系統運作時候,就需要加載一些配置和屬性,這些配置和屬性是一定存在了,又是公共的,同時需要在整個生命周期中都存在,是以隻需要一份就行,這個時候如果需要我再需要的時候new一個,再給他配置設定值,顯然是浪費記憶體并且再指派沒什麼意義,是以這個時候我們就需要單例模式或靜态方法去維持一份且僅這一份拷貝,但此時這些配置和屬性又是通過面向對象的編碼方式得到的,我們就應該使用單例模式,或者不是面向對象的,但他本身的屬性應該是面對對象的,我們使用靜态方法雖然能同樣解決問題,但是最好的解決方案也應該是使用單例模式。

  從功能上講:單例模式可以控制單例數量;可以進行有意義的派生;對執行個體的建立有更自由的控制;

  三、其他:

  資料庫連接配接能不能做singleton?

  如果是簡單地把一個connection對象封存在單例對象中,這樣是錯誤的,是以連接配接池裡有多個連結可以用,如果使用singleton,那在web通路時,就隻能用一個資料庫連結,那不是死的很慘?

  但是連結池可以使用單例模式,初始化的時候建立譬如100個connection對象,然後再需要的時候提供一個,用過之後傳回到pool中,我們用單例模式,是保證連接配接池有且隻有一個。

  再舉個例子,比如dal層寫好一個調用資料庫表的類,在bll層應用此類時,如果每次都new建立的話需要頻繁的建立和回收,而dal層這個類裡又沒有和對象相關的值變量,是以不需要每次都new一個,這時候就可以用單例模式來建立這個dal執行個體。

為什麼要用單例模式?

繼續閱讀