【朝花夕拾】設計模式之單例模式

来源:https://www.cnblogs.com/edison0621/archive/2019/04/05/10658368.html
-Advertisement-
Play Games

單例模式簡介 單例模式是GOF 23個設計模式中最簡單的模式了,它提供了一種創建唯一對象的最佳實現,註意此處的簡單隻是表述和意圖很簡單,但是實現起來,尤其是實現一個優美的單例模式卻沒有那麼簡單。 單例模式歸根結底就是要確保一個類只有一個實例,並提供一個全局方式來訪問該實例。具體而言,這種模式涉及到一 ...


 

單例模式簡介

單例模式是GOF 23個設計模式中最簡單的模式了,它提供了一種創建唯一對象的最佳實現,註意此處的簡單隻是表述和意圖很簡單,但是實現起來,尤其是實現一個優美的單例模式卻沒有那麼簡單。

單例模式歸根結底就是要確保一個類只有一個實例,並提供一個全局方式來訪問該實例。具體而言,這種模式涉及到一個類,並由這個類創建自己的對象,同時確保只有單個對象被創建,並提供唯一一種方式來訪問該對象的實例。

在現實生活中,單例的場景有很多,比如一夫一妻制(當然不道德的除外),比如一個部門只有一個領導等等。

單例模式UML類圖

danli_thumb[2]

 如上圖所示:

1、單例類只能有一個實例。

2、單例類必須自己創建自己的唯一實例。

3、單例類必須給所有其他對象提供這一實例。

4、構造函數是私有的。

範例

Double-Check

我們先看一個非常流行而又簡單的實現

   1:  public sealed class Singleton
   2:  {
   3:      private static Singleton instance = null;
   4:      private static readonly object padlock = new object();
   5:   
   6:      private Singleton()
   7:      {
   8:      }
   9:   
  10:      public static Singleton Instance
  11:      {
  12:          get
  13:          {
  14:              if (instance == null)
  15:              {
  16:                  lock (padlock)
  17:                  {
  18:                      if (instance == null)
  19:                      {
  20:                          instance = new Singleton();
  21:                          //Do a heavy task
  22:                      }
  23:                  }
  24:              }
  25:              return instance;
  26:          }
  27:      }
  28:  }

上述解決方案上,使用到了Double-Check方式,Double-Check方式可以說是盛名已久了,線程A與線程B在Null Check時同時通過,但是在Lock時,只能進入一個線程,其他線程都要等著。

這種方式在Java中編寫單例模式的時候是失效的,具體原因我沒有去深究。這一塊記憶體屏障技術(Memory Barrier),不過這段涉及到底層操作,一般很難有人會顯式操作,而且這段的控制異常複雜。另外一點就是,如果單例過程中操作的是一個數組或者其他對象,那麼在實例化後如果需要進行賦值等運算操作的,那麼其他線程在進行Null Check的時候就不會再次進入,如果其他線程調用了這個單例對象的某個屬性,這極有可能出現難以預測的bug

單例模式載入數據到記憶體,那麼如果我們需要在使用的時候再去載入到記憶體,而不是一開始就載入到記憶體,這樣可以節省記憶體空間。接下來我們看一下如何通過懶載入方式實現單例模式。

靜態類

採用靜態類實現單例模式,這並不是一種完全的懶載入,但依然是線程安全的
   1:  public sealed class Singleton
   2:  {
   3:      private static readonly Singleton instance = new Singleton();
   4:   
   5:      static Singleton()
   6:      {
   7:   
   8:      }
   9:   
  10:      private Singleton()
  11:      {
  12:   
  13:      }
  14:   
  15:      public static Singleton Instance
  16:      {
  17:          get
  18:          {
  19:              return instance;
  20:          }
  21:      }
  22:  }

C#中的靜態構造函數僅在創建類的實例或引用靜態成員時執行,並且每個AppDomain只執行一次,因為每次都需要對新構造的類型執行這種檢查,所以這種方式要比Double-Check方式更快。然而,也有一些問題:

  • 它不像其他實現那樣懶惰。尤其是,如果您有實例以外的靜態成員,那麼對這些成員的第一個引用將涉及創建實例。這將在下一個實現中得到糾正。
  • 如果一個靜態構造函數調用另一個靜態構造函數,而另一個靜態構造函數再次調用第一個靜態構造函數,則會出現複雜情況。需要註意,靜態構造函數在一個迴圈中相互引用的後果。
  • 只有當類型沒有被[beforefieldinit]標記時,.NET才能保證類型初始值設定項的惰性。不幸的是,C編譯器(至少在.NET 1.1運行時中提供)將沒有靜態構造函數的所有類型(即看起來像構造函數但被標記為靜態的塊)標記為beforefieldinit。需要註意beforefieldinit會影響性能,beforefieldinit的具體用法可以參見MSDN。

對於這個實現,許多人更喜歡擁有一個屬性,以防將來需要進一步的操作,並且JIT內聯可能使性能相同。另外有一種快捷方式就是,可以將實例設置為公共的靜態只讀變數,不設置為屬性,這樣代碼的基本框架會顯得非常小。(註意,如果需要惰性,靜態構造函數本身仍然是必需的。)

內部類

採用內部類,這是一種完全的懶載入。

   1:  public sealed class Singleton
   2:  {
   3:      private Singleton()
   4:      {
   5:   
   6:      }
   7:   
   8:      public static Singleton Instance { get { return Nested.instance; } }
   9:   
  10:      private class Nested
  11:      {
  12:          static Nested()
  13:          {
  14:          
  15:          }
  16:   
  17:          internal static readonly Singleton instance = new Singleton();
  18:      }
  19:  }

在這裡,嵌套類的靜態成員在第一次引用的時候會進行實例化操作,並且該引用只在實例中發生。這意味著實現是完全懶惰的,但具有前一個實現的所有性能優勢。請註意,儘管嵌套類可以訪問內部類的私有成員,但反過來卻不是,因此需要在此處對實例進行內部訪問。不過,這並不會引發任何問題,因為類本身是私有的。不過此處貌似顯得有點複雜。

Lazy

那麼有沒有其他方式優雅而又安全的實現單例模式呢,答案是有的,那就是通過Lazy方式,Lazy方式可以擁有更高的性能,因為實例只有在使用的時候才會真正創建對象,這就在很大程度上減少了記憶體的占用,當然,比較如果是比較簡單的單例創建,可以忽略這條不利影響。

Lazy自帶Double-Check,是線程安全的,他就像一個盾牌,在創建過程中,不管是創建簡單對象還是複雜對象,都不會允許其他線程使用尚未創建完成的對象,更多的Lazy使用,請參考MSDN。

   1:  public sealed class Singleton
   2:  {
   3:      private Lazy<Singleton> lazy;
   4:   
   5:      public Singleton Instance { get { return lazy.Value; } }
   6:   
   7:      public string Name{get;set;}
   8:   
   9:      public Singleton()
  10:      {
  11:          lazy = new Lazy<Singleton>(InitializeSingleton);
  12:      }
  13:   
  14:      private Singleton InitializeSingleton()
  15:      {
  16:          Singleton singleton = new Singleton();
  17:          singleton.Name="Test";
  18:          return singleton;
  19:      }
  20:  }

單例模式優缺點

優點:

全局範圍內只有一個實例,避免了記憶體消耗,以及實例頻繁的創建和銷毀

避免了對資源的多重占用,比如獨占式場景中

 

缺點:

一旦對象指向的外部環境發生了變化,比如在網路調用、MQ等場景中一般可以可以採用單例,但是這裡需要提醒的是,如果DNS發生異常,在異常期間將會出現極難修複的情況,除非手動重啟並指向新的域伺服器

這一點有點違反單一職責原則,通常情況下,一個類應該只關註自身邏輯而不是創建對象

沒有介面,無法繼承

本文參考了https://csharpindepth.com/articles/Singleton,該文也是深入理解C#的作者所寫,可以收藏此網站以便更快的獲取相關信息。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、可變與不可變類型 二、數字類型 1、用途 2、定義方式 3、總結 三、字元串類型 1、作用 2、定義方式 3、常用操作+內置方法 # 優先掌握的操作: 4、總結 四、列表類型 1、用途 2、定義方式 3、常用操作+內置的方法 4、總結 ...
  • libmxml是一個開源、小巧的C語言xml庫。這裡簡單分析一下它是用什麼樣的數據結構來保存分析過的xml文檔。 mxml關鍵的結構體mxml_node_t是這樣的實現的: 它使用左孩子右兄弟的樹形結構來描述xml報文:即下層節點登記在child鏈表,兄弟節點登記在next鏈表。 如果某個節點下麵有 ...
  • 格式:\033[顯示方式;前景色;背景色m + 結尾部分:\033[0m ...
  • 轉自博客:https://www.cnblogs.com/CreateMyself/p/9235968.html 前言 最近發表的EF Core貌似有點多,可別誤以為我只專攻EF Core哦,私下有時間也是一直在看ASP.NET Core的內容,所以後續會穿插講EF Core和ASP.NET Cor ...
  • 本文使用xUnit對ASP.NET Core WebAPI做單元測試,使用HttpClient的同步和非同步請求,下麵詳細介紹xUnit的使用過程: 一、創建示例項目 模板為我們自動創建了一個ValuesController控制器,保留裡面的一個Get請求和Post請求方法,代碼如下: 使用.NET ...
  • 中介者模式簡介 提供一個中介對象出來,用於封裝一系列對象的交互,從而使各對象不需要直接交互,進一步降低了對象間的耦合度。這是一種行為型設計模式。 由此可見,中介者模式主要解決的是對象間所存在的大量關係,我們都知道,對象間一旦關聯緊密,必然會導致系統的複雜性增加,一旦某個對象有所修改,其關聯對象也有可 ...
  • 如果您的孩子不適應編譯型語言怎麼辦? 如果您的孩子貪玩不想花多時間在編程上怎麼辦? 如果您還沒有孩子怎麼辦? 如果您夜晚兼職覺不夠睡又怎麼辦? 不妨試試 “ 拍 簧 片 ”。 媽了巴子的有點麻煩,但別怕,接下來我將用一把梭帶你把這個“場子“搭起來: 使用VSCode搭建“拍簧片”環境: 1、裝VsC ...
  • 項目說明 1. 目前支持WebForm文件下載,後續支持Mvc 2. 支持下載文件加密以及下載限速 3. 項目源碼: "MasterChief.DotNet.Framework.Download" 4. Nuget:Install Package MasterChief.DotNet.Framewo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...