設計模式(1)單例模式(Singleton)

来源:http://www.cnblogs.com/fonour/archive/2017/04/22/6747430.html
-Advertisement-
Play Games

設計模式(0)簡單工廠模式 源碼地址 0 單例模式簡介 0.0 單例模式定義 單例模式是GOF二十三中經典設計模式的簡單常用的一種設計模式,單例模式的基本結構需滿足以下要求。 單例模式的核心結構只有一個單例類,單例模式要保證這個類在運行期間只能被實例化一次,即只會被創建唯一的一個單例類的實例。 單例 ...


設計模式(0)簡單工廠模式

源碼地址

0 單例模式簡介

0.0 單例模式定義

單例模式是GOF二十三中經典設計模式的簡單常用的一種設計模式,單例模式的基本結構需滿足以下要求。

  • 單例模式的核心結構只有一個單例類,單例模式要保證這個類在運行期間只能被實例化一次,即只會被創建唯一的一個單例類的實例。
  • 單例模式需要提供一個全局唯一能得到這個類實例的訪問點,一般通過定義一個名稱類似為GetInstance的公用方法實現這一目的。

要滿足上面的兩點要求,應該很容易的想到:

1.該類的構造函數應該是私有的,不能隨意被實例化是保證只有一個實例的前提。

2.該類需提供一個公開的且返回值類型為單例類類型的公用方法。

來看一下單例模式的基本結構圖:

1

0.1 單例模式應用場景

通過上面對單例模式基本定義的瞭解,單例模式的應用場景也就很明確了。

單例模式適用於各種系統中某個類的對象只能存在一個類似場景, 我們現在回顧一下上一篇簡單工廠模式中的大致實現

/// <summary>
 /// 簡單工廠類
 /// </summary>
 public class Factory
 {

     /// <summary>
     /// 創建英雄的靜態方法
     /// </summary>
     /// <param name="heroName">英雄名稱</param>
     /// <returns></returns>
     public static IHero CreateHero(string heroName)
     {
         switch (heroName)
         {
             case "DH":
                 return new DH();
             case "WD":
                 return new WD();
             case "KOG":
                 return new KOG();
             case "POM":
                 return new POM();
             default:
                 return null;
         }
     }
 }
/// <summary>
 /// 惡魔獵手
 /// </summary>
 public class DH : IHero
 {

     /// <summary>
     /// 秀出自己的技能
     /// </summary>
     public void ShowSkills()
     {
         Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。");
     }
 }

通過簡單工廠模式確實達到了介面隔離的目的,外部使用無需關註內部類的具體實現工程,只通過簡單工廠類創建想要的對象即可,但這裡有一個致命的問題就是,我們玩兒游戲的過程中,英雄會存在一個死亡和複活的場景,我們簡單的把英雄祭壇理解為創建英雄的簡單工廠,假設當我們複活英雄的時候,是通過工廠類創建英雄的一個過程,那麼我們面臨的問題就出現了,我本來一個6級的大惡魔獵手,由於走位過度風騷,走進了祭壇,現在在通過工廠創建的時候,由於是又重新new了一個對象,從祭壇中走出了一個萌叉叉的1級小惡魔獵手……

為保證我的那個6級大惡魔還是那個6級大惡魔,一身裝備一個不少的走出祭壇,至此也就到了必須引入單例模式的時候了。

1 單例模式詳解

1.0單例模式的基本實現-懶漢式單例模式

按照單例模式的2個基本特征:私有的構造函數公開的GetInstance方法。將DH類進行如下改造,代碼的具體意圖已經通過註釋詳細解釋。

/// <summary>
/// 惡魔獵手
/// </summary>
public class DH : IHero
{
    //定義一個靜態的DH類變數
    private static DH dh;

    /// <summary>
    /// 私有的構造函數,能夠保證該類不會在外部被隨意實例化,是保證該類只用一個實例的基本前提
    /// </summary>
    private DH()
    {

    }

    /// <summary>
    /// 定義一個靜態的公開的GetInstance方法供外部得到DH類唯一實例是調用
    /// </summary>
    /// <returns></returns>
    public static DH GetInstance()
    {
       //先判斷dh是否已經被實例化,若未被實例化,先實例化得到DH類的實例
        //保證DH類只被實例化一次
        if (dh == null)
        {
            dh = new DH();
        }
        return dh;
    }

    /// <summary>
    /// 秀出自己的技能
    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。");
    }
}

修改Factory簡單工廠類中創建DH實例部分的代碼

/// <summary>
/// 簡單工廠類
/// </summary>
public class Factory
{

    /// <summary>
    /// 創建英雄的靜態方法
    /// </summary>
    /// <param name="heroName">英雄名稱</param>
    /// <returns></returns>
    public static IHero CreateHero(string heroName)
    {
        switch (heroName)
        {
            case "DH":
                return DH.GetInstance(); //通過DH類公開的靜態GetInstance方法得到DH類的實例
            case "WD":
                return new WD();
            case "KOG":
                return new KOG();
            case "POM":
                return new POM();
            default:
                return null;
        }
    }
}

客戶端測試

static void Main(string[] args)
{
    IHero dh1 = Factory.CreateHero("DH");
    IHero dh2 = Factory.CreateHero("DH");
    if (dh1.Equals(dh2))
        Console.WriteLine("惡魔獵手:我還是從前的我。");
    else
        Console.WriteLine("惡魔獵手:我已不是從前的我。");

    IHero wd1 = Factory.CreateHero("WD");
    IHero wd2 = Factory.CreateHero("WD");
    if (wd1.Equals(wd1))
        Console.WriteLine("守望者:我還是從前的我。");
    else
        Console.WriteLine("守望者:我已不是從前的我。");

    Console.ReadLine();
}

輸出結果如下

1

至此我們對DH這個類應用了單例模式來確保無論何時走出祭壇的都是同一個DH對象,從DH對象被實例化的實際來看,是在被使用的時候才會被創建,這種方式被成為懶漢式單例模式

有一天突發奇想,我建造兩個英雄祭壇(兩個簡單工廠類),用我APM500+的超快手速,同時在兩個祭壇里生產同一個英雄,發現我擁有了2個6級大惡魔……(當然了,實際中不會有這個bug存在)

這就是基本懶漢式單例模式要面對的多線程問題,也就是說基本懶漢式單例模式的寫法是無法做到線程級別安全的

問題的關鍵就在獲取DH類實例的GetInstance方法的內部實現中

if (dh == null)
{
   dh = new DH();
}
return dh;

簡單來說就是當第一個線程調用判斷if(dh==null)為true,已經進入內部通過調用new進行實例化時,另一個線程也進行了判斷,而恰恰此時dh還沒有被實例化完成,同樣第二個線程也進入if判斷語句的內部,進行dh的實例化,於是就出現了2個DH類的實例,從兩個祭壇走出來兩個大惡魔。

解決這一問題一般有兩種方法餓漢式單例雙重檢查鎖。

1.1 餓漢式單例

餓漢式單例是在系統初始化時自動完成單例類實例的一種方法,而不是等到需要的時候再初始化,也就是說不管以後你會不會用到這個類的對象,我都會給你實例化一個出來,有一種饑餓難耐的感覺在裡面,故名餓漢式。

/// <summary>
/// 餓漢式單例
/// </summary>
public class DH : IHero
{
    //系統初始化時已經將DH類進行實例化
    private static readonly DH dh = new DH();

    /// <summary>
    /// 私有的構造函數,能夠保證該類不會在外部被隨意實例化,是保證該類只用一個實例的基本前提
    /// </summary>
    private DH()
    {

    }

    /// <summary>
    /// 調用時直接返回已經實例化完成的對象
    /// </summary>
    /// <returns></returns>
    public static DH GetInstance()
    {
        return dh;
    }

    /// <summary>
    /// 秀出自己的技能
    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。");
    }
}

這種方法簡單直接的解決了線程安全問題,但是由於實在初始化時就將單例類進行了實例化,一定程度上造成了各種資源的浪費,違背了延遲載入的設計思想,一般為瞭解決單例模式線程安全問題,通常使用雙重檢查鎖的方法。

1.2 雙重檢查鎖

雙重檢查鎖的命名基於單重檢查鎖方式而來,單重檢查鎖是在GetInstance實現的時候先行進行鎖定,防止別的線程進入,從而解決線程安全問題的。主要代碼如下

//定義一個靜態只讀的用於加鎖的輔助對象
private static readonly object lockObject = new object ();
lock (lockObject)
{
    //先判斷dh是否已經被實例化,若未被實例化,先實例化得到DH類的實例
    //保證DH類只被實例化一次
    if (dh == null)
    {
        dh = new DH();
    }
}
return dh;

這種方式每次都要進行lock操作,實際上是一種同步方式,這將會在一定程度上影響系統性能的瓶頸和增加了額外的開銷。由此衍生出了雙重檢查鎖的方式,簡單來說就是先判斷一次dh是否為null,為null時才進行lock操作,不為null就直接返回。

/// <summary>
/// 惡魔獵手
/// </summary>
public class DH : IHero
{
    //定義一個靜態的DH類變數
    private static DH dh;
    //定義一個靜態只讀的用於加鎖的輔助對象
    private static readonly object lockObject = new object (); 
    /// <summary>
    /// 私有的構造函數,能夠保證該類不會在外部被隨意實例化,是保證該類只用一個實例的基本前提
    /// </summary>
    private DH()
    {

    }

    /// <summary>
    /// 定義一個靜態的公開的GetInstance方法供外部得到DH類唯一實例是調用
    /// </summary>
    /// <returns></returns>
    public static DH GetInstance()
    {
        //先判斷dh是否已經被實例化,若未被實例化,先加鎖保證線程安全
        if (dh == null)
        {
            lock (lockObject)
            {
              //先判斷dh是否已經被實例化,若未被實例化,先實例化得到DH類的實例
                //保證DH類只被實例化一次
                if (dh == null)
                {
                    dh = new DH();
                }
            }
        }
        return dh;
    }

    /// <summary>
    /// 秀出自己的技能
    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。");
    }
}

2 總結

本次主要基於上一篇的簡單工廠模式,延續的學習使用了單例工廠模式確保一個類實例的全局唯一性,過程中學習了懶漢式、餓漢式、雙重檢查鎖等具體解決方案及演變過程。

設計模式從來不是單打獨鬥,核心思想是要根據實際需要利用多種模式互相配合來實現代碼結構的最優化和健壯性。


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

-Advertisement-
Play Games
更多相關文章
  • 下麵列出了當前可用的 PCRE 修飾符。括弧中提到的名字是 PCRE 內部這些修飾符的名稱。 模式修飾符中的空格,換行符會被忽略,其他字元會導致錯誤。 Warning This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7. ...
  • 讓我們來回憶下上次你是怎麼發佈你的代碼的: 1. 先把線上的代碼用ftp備份下來 2. 上傳修改了的文件 3. 測試一下功能是否正常 4. 網站500了,趕緊用備份替換回去 5. 替換錯了/替換漏了 6. 一臺伺服器發佈成功 7. 登錄每一臺執行一遍發佈操作 8. 加班搞定 9. 老闆發飆 ... ...
  • 1225 八數位難題 時間限制: 1 s 空間限制: 128000 KB 題目等級 : 鑽石 Diamond 題解 查看運行結果 1225 八數位難題 1225 八數位難題 時間限制: 1 s 空間限制: 128000 KB 題目等級 : 鑽石 Diamond 時間限制: 1 s 空間限制: 128 ...
  • 第五章感覺是第四章的練習項目,無非就是多了一個模擬登錄。 不分小節記錄了,直接上知識點,可能比較亂。 1.常見的httpcode: 2.怎麼找post參數? 先找到登錄的頁面,打開firebug,輸入錯誤的賬號和密碼,觀察post_url變換,從而確定參數。 3.讀取本地的文件,生成cookies。 ...
  • 前言 線程池是併發中一項常用的優化方法,通過對線程復用,減少線程的創建,降低資源消耗,提高程式響應速度。在 Java 中我們一般通過 Exectuors 提供的工廠方法來創建線程池,但是線程池的最終實現類是 ThreadPoolExecutor,下麵我們詳細分析一下 ThreadPoolExecut ...
  • 此文章主要有以下幾個知識點: 一.如何創建 Maven的Web 工程 二.整合SSM(Spring,SpringMvc,Mybatis),包括所有的配置文件 三.用 mybatis 逆向工程生成對應的文件 四.用spring test 單元測試測試最後的整合結果 一.如何創建 Maven的Web 工 ...
  • 學習一門語言都要打好基礎,前面的知識可能看著無聊,但是很重要,能夠讓我們打好堅實的基礎,一定要掌握int、float、long、字元串、列表、元組、集合、字典、函數和類的基礎常用的操作。 下麵來看一看float數據類型都有那些常用的操作,以及和int不一樣的地方: 1.as_integer_rati ...
  • 1 概念定義 1 概念定義 1.1 定義 定義一個用於創建對象的介面,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。 1.2 類型 創建類模式 2 原理特征 2 原理特征 2.1 類圖 2.2 優點 1)封裝性良好,代碼結構清晰 2)可拓展性高,只需修改一下工廠方法或拓展一個工廠類 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...