設計模式系列-單例模式


        今天單位有自己的食堂啦,發郵件收了工卡之后統一拿去激活,以后就用工卡去食堂吃飯啦,早上2元,中午10元,晚上3元,都是自助噢,很爽,不過還是有一推人沒有第一時間激活卡,也有的人激活卡了忘記自己激活了,我就是其中一個,無奈下我只好到食堂自己去激活卡了,餐廳只有一個機會卡的櫃台所以激活的時候需要排隊,還好我來的早,提前搞定,剛出門時就看又一批激活工卡的人來啦。

        下班后。不例外繼續到食堂吃飯,便宜嘛自然人也就不少,當然也包括激活工卡的人,吃飯是心里就暗自想,這不就是設計模式里面的一種場景嗎? 一個餐廳只有一個激活卡的櫃台。在餐廳的作用域內提供了一個全局的訪問點。

        1.模擬該場景:

          首先分析一個這個場景,首先:

          ① 我們需要一個餐廳類(Restaurant)。

          ② 接下來我們還要設計一個櫃台類(Counter),餐廳包含櫃台,並且只能擁有一個櫃台。

          ③ 櫃台類中包含一個用來激活工卡(ActivationCard)的功能,好讓員工可以在餐廳用餐。

          廢話不多說模擬場景代碼如下:

    // 餐廳類
     public  class Restaurant
    {
         // 餐廳包含櫃台
         public  static Counter counter =  new Counter();
    }

     // 櫃台類
     public  class Counter
    {
         // 激活工卡方法
         public  void ActivationCard()
        {
             // 激活工卡的過程
            Console.WriteLine( " 工卡激活成功! ");
        }
    }

     static  void Main( string[] args)
    {
           // 此時前往餐廳辦理激活工卡
            Counter counter = Restaurant.counter;
          counter.ActivationCard();
    }

       這時,似乎不出來代碼中有什么問題,但是仔細想想,如果這個時候我在調用激活工卡的時候將獲取到的櫃台對象重新實例化一次呢?仿佛憑空多制造出了一個櫃台的感覺一樣,為了確保我們在同一個范圍內只有一個櫃台實例我們可以考慮單例模式。

       2.引入單例模式

         那么如何讓同一個范圍內的對象僅有一個實例呢?

         我們可以類的定義中做手腳,改裝后的櫃台類如下:

    // 櫃台類
     public  class Counter
    {
         // 定義一個私有的靜態櫃台
         private  static Counter instance;

         // 私有構造方法,禁止外部實例化該類型
         private Counter()
        {
        }

         public  static Counter GetCounterInstance()
        {
             if (instance ==  null)
            {
                 // 類型內部可以實例化私有構造實例
                instance =  new Counter();
            }
             return instance;
        }

         // 激活工卡方法
         public  void ActivationCard()
        {
             // 激活工卡的過程
            Console.WriteLine( " 工卡激活成功! ");
        }
    }

       那么櫃台是屬於餐廳的,必然要給餐廳中安裝櫃台,平且讓餐廳提供激活工卡的服務。餐廳類代碼如下:

    // 餐廳類
     public  class Restaurant
    {
         // 激活工卡服務,返回櫃台對象(對應顯示現實中,指向櫃台的位置)
         public Counter ActivationCardService()
        {
             return Counter.GetCounterInstance();
        }
    }

       主函數調用代碼如下:

       static  void Main( string[] args)
      {
             // 餐廳實例
            Restaurant res =  new Restaurant();
             // 進入餐廳找到櫃台,激活工卡
            res.ActivationCardService().ActivationCard();
      }

       這時一個最基本的單例模式就體現出來了,前面介紹過,單例模式:就是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

         現在看來上面的代碼確實做到了這一點,但是在多線程情況下會怎樣呢?我們來看獲得餐廳實例的方法,這個方法里面實現了僅創建一個實例的邏輯,代碼如下:

      public  static Counter GetCounterInstance()
     {
             if (instance ==  null)
            {
                 // 類型內部可以實例化私有構造實例
                instance =  new Counter();
            }
             return instance;
     }

         如果這是兩個線程同時訪問,那么就會造成同時創建了兩個實例,因為兩個線程到達這段代碼時,該實例都是未創建的,那么如何解決這個問題呢?在實例化方法中加入一個臨界區是一個很好的解決辦法,這樣就能保證一個線程進去臨界區時,另一個線程在臨界區外面等待,代碼如下:

    private  static  readonly  object obj =  typeof( object);
         public  static Counter GetCounterInstance()
        {
             // 加入臨界區 當一個線程進入,后面線程等待
             lock (obj)
            {
                 if (instance ==  null)
                {
                     // 類型內部可以實例化私有構造實例
                    instance =  new Counter();
                }
            }
             return instance;
        }

 

       這個時候就解決了線程同步的問題了,在多線程環境下也會只有一個實例被創建,成為了真真正正的單例模式!

       那么在考慮一個問題,回到我們之前的激活工卡場景中,排隊的人中有很多卡已經激活自己卻不知道的,也就是說,當很多線程執行創建實例的代碼時,實例已經是創建過的,那么不必要實例化的線程我們能不能就不讓它們進入臨界區,減少系統的性能開銷?

        代碼如下:

private  static  readonly  object obj =  typeof( object);
         public  static Counter GetCounterInstance()
        {
             // 當實例未被實例化在進入臨界區
             if (instance ==  null)
            {
                 // 加入臨界區 當一個線程進入,后面線程等待
                 lock (obj)
                {
                     if (instance ==  null)
                    {
                         // 類型內部可以實例化私有構造實例
                        instance =  new Counter();
                    }
                }
            }
             return instance;
        }

       這下就是我們傳說中的單例模式了,並且對性能也進行了優化。

      3.C#版的單例模式

       那么根據C#的語言特性,實現單例模式又是什么樣子呢?

       ①我們可以吧GetCounterInstance()方法 替換成 C#的屬性。

       ②我們可以使用密封類,阻止類型的派生,避免多實例化對象。

       ③使用靜態並且只讀的私有字段存放類型的唯一實例

      代碼如下:

  // 櫃台類
     public  sealed  class Counter
   {
         // 在第一次調用類成員時,初始化唯一實例
         private  static  readonly Counter instance =  new Counter();

         private Counter()
        {
        }

         // 返回類型實例屬性
         public  static Counter Instance
        {
             get
            {
                 return instance;
            }
        }

         // 激活工卡方法
         public  void ActivationCard()
        {
             // 激活工卡的過程
            Console.WriteLine( " 工卡激活成功! ");
        }
   }

      暈。當我想完時,晚飯已經涼了。算了,這不就是程序員的生活嘛?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM