熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

設計模式之Singleton(單態模式)

2022-06-13   來源: Java高級技術 

  摘要討論 Singleton 設計模式(指示如何以及何時創建對象的創造性模式)及其在 Microsoft NET 框架中的有效使用

  內容

  簡介

  Singleton 模式

  結論

  簡介

  在開發軟件應用程序過程中隨著應用程序的開發會出現重復性的模式 隨著整個軟件系統的開發很多相同的模式會逐漸顯現出來

  這種重復性模式概念在其他應用中是非常明顯的 汽車制造就是一種此類應用 很多不同的汽車型號使用相同的子構件包括大多數基本部件(例如燈泡和緊固零件)以及較大的構件(例如底盤和發動機)

  在住宅建築中重復性模式概念適用於螺絲和螺釘以及整體總體建築物配電系統 無論組建的小組是為了開發新的汽車設計還是新的建築物設計它其通常不必沒有考慮到以前已解決的問題 如果設計和建築住宅的小組必須重新構思和設計房子的每一個組成部分則整個過程所花的時間比現在要長得多 門高或燈開關功能等許多設計決策(例如門高或燈開關功能)很容易理解 房為滿足給房子不同部分提供洗手功能的要求房屋設計師不必重新設計和重新建造不同類型的輸供水和蓄水設施以便達到為房子不同部分提供洗手功能的要求 標准水槽以及標准的熱水和冷水輸入接頭和排水輸出接頭是很容易理解非常常見的房屋建築構件 可以將重復性模式概念反復應用於我們周圍的幾乎每樣東西上包括軟件

  汽車和住宅建築示例有助於在軟件設計和構造中體現某些一般性的抽象概念 易於理解且明確定義的通用功能部件的概念是設計模式的源動力它也是其他兩篇設計模式文章探究工廠設計模式和探究觀察者設計模式的重點 這些模式幾乎涵蓋了面向對象的軟件設計的各個方面包括對象創建對象交互和對象生存期 在本文中我們將討論 Singleton 模式它包含在創造性模式系列中

  創造性模式指示如何以及何時創建對象 很多實例需要只能通過創造性方法解決的特殊行為而不是在創建實例後強制實施所需的行為 此類行為要求最好的例子之一包含在 Singleton 模式中 Singleton 模式在 Design Patterns: Elements of Reusable Software 這一經典參考書目中有正式的定義該書的作者包括 Erich GammaRichard HelmRalph Johnson 和 John Vlissides(也稱為四人組或 GoF) 在 Design Patterns 中此模式是最簡單也是使用最廣泛的模式之一 但是正如我們將會看到的一樣在實現此模式時可能會出現一些問題 本文試圖通過 Singleton 模式的多個早期實現來從頭開始分析 Singleton 模式以及如何在 Microsoft_ NET 應用程序開發中發揮其最佳用途

  Singleton 模式

  按照 Design Patterns 中的定義Singleton 模式的用途是 ensure a class has only one instance and provide a global point of access to it(確保每個類只有一個實例並提供它的全局訪問點)

  它可以解決什麼問題或者換句話說我們使用它的動機是什麼? 幾乎在每個應用程序中都需要有一個從中進行全局訪問和維護某種類型數據的區域 在面向對象的 (OO) 系統中也有這種情況在此類系統中在任何給定時間只應運行一個類或某個類的一組預定義數量的實例 例如當使用某個類來維護增量計數器時此簡單的計數器類需要跟蹤在多個應用程序領域中使用的整數值 此類需要能夠增加該計數器並返回當前的值 對於這種情況所需的類行為應該僅使用一個類實例來維護該整數而不是使用其它類實例來維護該整數

  最初人們可能會試圖將計數器類實例只作為靜態全局變量來創建 這是一種通用的方法但實際上只解決一部分問題它解決了全局可訪問性問題但沒有采取任何措施來確保在任何給定的時間只運行一個類實例 應該由類本身來負責只使用一個類實例而不是由類用戶來負責 應該始終不要讓類用戶來監視和控制運行的類實例的數量

  所需要的是使用某種方法來控制如何創建類實例然後確保在任何給定的時間只創建一個類實例 這會確切地給我們提供所需的行為並使客戶端不必了解任何類細節

  邏輯模型

  Singleton 模型非常簡單直觀 (通常)只有一個 Singleton 實例 客戶端通過一個已知的訪問點來訪問 Singleton 實例 在這種情況下客戶端是一個需要訪問唯一 Singleton 實例的對象 以圖形方式顯示此關系

  


   Singleton 模式邏輯模型

  物理模型

  Singleton 模式的物理模型也是非常簡單的 但是隨著時間的推移實現 Singleton 的方式也略有不同 讓我們看一下原始的 GoF Singleton 實現 顯示按 Design Patterns 所定義的原始 Singleton 模式的 UML 模型

  


   Design Patterns 中的 Singleton 模式物理模型

  我們看到的是一個簡單的類圖表顯示有一個 Singleton 對象的私有靜態屬性以及返回此相同屬性的公共方法 Instance() 這實際上是 Singleton 的核心 還有其他一些屬性和方法用於說明在該類上允許執行的其他操作 為了便於此次討論讓我們將重點放在實例屬性和方法上

  客戶端僅通過實例方法來訪問任何 Singleton 實例 此處沒有定義創建實例的方式 我們還希望能夠控制如何以及何時創建實例 在 OO 開發中通常可以在類的構造函數中最好地處理特殊對象的創建行為 這種情況也不例外 我們可以做的是定義我們何時以及如何構造類實例然後禁止任何客戶端直接調用該構造函數 這是在 Singleton 構造中始終使用的方法 讓我們看一下 Design Patterns 中的原始示例 通常將下面所示的 C++ Singleton 示例實現代碼示例視為 Singleton 的默認實現 本示例已移植到很多其他編程語言中通常它在任何地方的形式與此幾乎相同

  C++ Singleton 示例實現代碼

   // Declaration
class Singleton {
 public: static Singleton* Instance();
 protected: Singleton();
 private: static Singleton* _instance;
}
// Implementation
Singleton* Singleton::_instance = ;
Singleton* Singleton::Instance() {
 if (_instance == ) {
   _instance = new Singleton;
 }
return _instance;

  讓我們先花點時間分析一下此代碼 該簡單類有一個成員變量此變量是指向該類自身的指針 注意構造函數是受保護的並且只有公共方法才是實例方法 在實例方法實現中有一個控制塊 (if)它檢查成員變量是否已初始化如果沒有的話則創建一個新實例 控制塊中這種惰性初始化意味著僅在第一次調用 Instance() 方法時初始化或創建 Singleton 實例 對於很多應用程序這種方法效果很好 但對於多線程應用程序這種方法證明具有潛在危險的副作用 如果兩個線程同時進入控制塊則可能會創建該成員變量的兩個實例 要解決這一問題您可能想只將重要部分放在控制塊周圍以確保線程安全 如果您這樣做則將對實例方法的所有調用進行序列化處理並且可能會對性能產生不利影響(取決於應用程序) 正是由於這個原因創建了此模式的另一個版本它使用某種稱為雙重檢驗機制的功能 下一個代碼示例顯示使用 Java 語法的雙重檢驗鎖定

  使用 Java 語法的雙重檢驗鎖定 Singleton 代碼

   // C++ port to Java
class Singleton {
 public static Singleton Instance() {
   if (_instance == null) {
    synchronized (ClassforName(Singleton)) {
     if (_instance == null) {
     _instance = new Singleton();
    }
   }
  }
return _instance;
}
protected Singleton() {
}
private static Singleton _instance = null;

  在使用 Java 語法的雙重檢驗鎖定 Singleton 代碼示例中我們直接將 C++ 代碼移植到 Java 代碼以便利用 Java 關鍵部分塊(已同步) 主要差別是不再有單獨的聲明和實現部分沒有指針數據類型並且采用了新的雙重檢驗機制 雙重檢驗發生在第一個 IF 塊上 如果成員變量為空則執行進入關鍵部分塊該塊再次雙重檢驗該成員變量 僅在通過此最終測試後才會實例化該成員變量 一般來說兩個線程無法使用這種方法創建兩個類實例 另外因為在第一次檢查時沒有出現線程阻塞所以對此方法的大多數調用不會由於必須進入鎖定而導致性能下降 目前在實現 Singleton 模式時很多 Java 應用程序中都廣泛使用這種方法 這種方法很巧妙但也有瑕疵 某些優化編譯器可以將惰性初始化代碼優化掉或對其重新進行排序並且會重新產生線程安全問題 有關更深入的解釋請參閱 The DoubleCheck Locking is Broken Declaration

  另一種試圖解決此問題的方法可能是在成員變量聲明中使用 volatile 關鍵字 這應該告訴編譯器不要對代碼重新排序並且放棄優化 目前這是唯一建議的 JVM 內存模型並且不會立即解決該問題

  實現 Singleton 的最好方法是什麼? 最終(而不是碰巧)Microsoft NET 框架解決了所有這些問題從而更易於實現 Singleton卻不會產生我們目前討論的不利副作用 NET 框架以及 C# 語言允許我們在必要時通過替換語言關鍵字將上述的 Java 語法移植到 C# 語法 因此Singleton 代碼變為以下內容

  以 C# 編碼的雙重檢驗鎖定

   // Port to C#
class Singleton {
 public static Singleton Instance() {
   if (_instance == null) {
   lock (typeof(Singleton)) {
    if (_instance == null) {
     _instance = new Singleton();
    }
   }
  }
return _instance;
}
protected Singleton() {
}
private static volatile Singleton _instance = null;

  此處我們替換了鎖定關鍵字來執行關鍵部分塊使用 typeof 操作並添加 volatile 關鍵字以確保沒有對代碼進行優化程序重新排序 雖然此代碼或多或少是 GoF Singleton 模式的直接移植但它可達到我們的目的並且我們可獲得所需的行為 此代碼還說明了將 C++ 移植到 Java 和將 Java 移植到 C# 代碼的一些相似之處和主要差別 但是正如任何代碼移植一樣通常目標語言或平台的一些優點可能在移植過程中失去 需要做的就是對代碼重構以便利用新目標語言或平台的功能

  在前面的每個代碼示例中Singleton 的原始實現隨時間的推移而發生變化以解決在每個新模式實現中發現的問題 一些問題(例如線程安全)要求對大多數實現進行更改以滿足在目前應用程序中日益增長的需要並解決演變發展問題 NET 在應用程序開發中提供了一個演變步驟 可以在框架級別解決前面示例中出現的很多亟待解決的問題而不是在實現級別解決 雖然上一個示例顯示了一個使用 NET 框架和 C# 的有效 Singleton 類但只需更好地利用 NET 框架本身就可以大大簡化此代碼 以下示例使用 NET它是一個松散地基於原始 GoF 模式的最小限度的 Singleton 類並且仍然可獲得類似的行為

  NET Singleton 示例

   // NET Singletonsealed
class Singleton {
 private Singleton() {
 }
 public static readonly Singleton Instance = new Singleton();

  此版本已大大簡化並且更加直觀 它仍然是 Singleton 嗎? 讓我們看一下更改了哪些內容然後再做決定 我們修改了要密封的類本身(該類密封後是不可繼承的)刪除了惰性初始化代碼刪除了 Instance() 方法並且對 _instance 變量做了大量的修改 對 _instance 變量所做的更改包括修改對公共方法的訪問級別將變量標記為只讀以及在聲明時初始化該變量 此處我們可以直接定義所需的行為而不關心實現的潛在有害的副作用 那麼使用惰性初始化有什麼優點以及使用多個線程有什麼危險呢? 在 NET 框架中內置了所有正確的行為 讓我們先看第一種情況惰性初始化

  最初使用惰性初始化的主要原因是要獲取僅在第一次調用 Instance() 方法中創建實例的行為還因為 C++ 規范中具有某種開放性並不定義靜態變量的確切初始化順序 要在 C++ 中獲得所需的 Singleton 行為必須采用涉及使用惰性初始化的運算方法 我們真正關心的是在第一次(在該情況下)調用實例屬性中創建該實例還是在此調用之前創建該實例的並且類中的靜態變量是否有已定義的初始化順序 對於 NET 框架這就是我們獲取的行為 在 JIT 過程中當(且僅當)任何方法使用靜態屬性時框架將初始化此靜態屬性 如果沒有使用該屬性則不會創建實例 更准確地說在 JIT 過程中發生的事情就是任何調用方使用該類的任何靜態成員時構造和加載該類 在這種情況下結果是相同的

  那麼線程安全初始化呢? 框架也解決了這一問題 框架內部保證靜態類型初始化的線程安全 換句話說在上面的示例中只創建一個 Singleton 類實例 還要注意用於保存類實例的屬性字段稱為實例 此選項更好地說明了在本文中的討論過程中此值是類的實例框架本身中雖然使用的屬性名稱稱為但有多個類使用此類型的 Singleton 概念完全相同

  對類所做的其他更改意味著禁止劃分子類 添加密封類修飾符可確保不會將該類劃分為子類 GoF Singleton 模式詳細介紹了試圖對 Singleton 劃分子類所產生的問題該劃分通常並不是小事 在大多數情況下可以很容易地開發沒有父類的 Singleton並且添加劃分子類功能會增加通常根本不需要的新的復雜性級別 隨著復雜性的提高測試培訓和文檔編制等所需的時間也會增加 通常除非絕對必要否則您不希望提高任何代碼的復雜性

  讓我們看一下如何使用 Singleton 使用我們最初的計數器的有關動機的概念我們可以創建一個簡單的 Singleton 計數器類並說明我們將如何使用它 顯示了 UML 類說明將包含什麼內容

  


   UML 類圖表

  相應的類實現代碼以及示例客戶端使用如下所示

  示例 Singleton 使用

   sealed class SingletonCounter {
 public static readonly SingletonCounter Instance = new SingletonCounter();
 private long Count = ;
 private SingletonCounter() {
 }
 public long NextValue() {
 return ++Count;
 }
}
class SingletonClient {
 [STAThread] static void Main() {
  for (int i=; i<; i++) {
   ConsoleWriteLine(Next singleton value: {} SingletonCounterInstanceNextValue());
  }
 }

  此處我們還創建了一個 Singleton 類來維護具有 long 類型的增量計數 客戶端是一個簡單的控制台應用程序它顯示計數器類的 個值 雖然此示例極其簡單但它卻說明了如何使用 NET 來實現 Singleton然後將其用在應用程序中

  小結

  Singleton 設計模式是一個非常有用的機制可用於在面向對象的應用程序中提供單個對象訪問點 無論使用的是什麼實現該模式提供一個大家所熟知的概念以便其在設計和開發小組之間方便地進行共享 但是正如我們所發現的一樣注意到這些實現有多大差異及其潛在的副作用也是非常重要的 NET 框架為模式實現者在設計所需的功能類型方面提供了很大的幫助實現者無需處理本文中所討論的很多副作用 在正確實現後可以證實模式的最初目的的有效性

  設計模式是非常有用的軟件設計概念可使小組將重點放在提供最佳類型的應用程序上而不考慮它們是什麼應用程序 關鍵在於正確而有效地使用設計模式目前有很多關於將設計模式用於 Microsoft NET 方面的 MSDN 系列文檔其中介紹了如何正確而有效地使用設計模式


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27646.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.