跳轉到內容

單例模式

25% developed
來自華夏公益教科書

代理 計算機科學設計模式
單例模式
狀態

術語 單例模式 指的是一個只能例項化一次的物件。如果需要:

  • 您需要全域性訪問資源,例如日誌記錄...
  • 您只需要一個實用程式類的例項,不想建立很多物件。

在某些應用程式中,強制執行物件的單一例項是合適的,例如:視窗管理器、列印後臺處理程式、資料庫訪問和檔案系統。

重新整理 Java 物件建立

在像 Java 這樣的程式語言中,您必須建立物件型別(通常稱為類)的例項才能在整個程式碼中使用它。舉個例子,這段程式碼

 Animal dog;                  // Declaring the object type
 dog = new Animal();          // Instantiating an object

這也可以寫成一行以節省空間。

 Animal dog = new Animal();   // Both declaring and instantiating

有時,您需要多個相同物件型別的例項。您可以建立多個相同物件型別的例項。例如

Animal dog = new Animal();
Animal cat = new Animal();

現在我已經有了兩種相同物件型別的例項,我可以在我的程式中分別使用 dogcat 例項。對 dog 的任何更改都不會影響 cat 例項,因為它們都已在單獨的記憶體空間中建立。要檢視這些物件是否真的不同,我們可以執行以下操作

 System.out.println(dog.equals(cat));  // output: false

程式碼返回 false,因為兩個物件都不同。與這種行為相反,單例模式 的行為有所不同。單例物件型別無法例項化,但您可以獲得物件型別的例項。讓我們使用 Java 建立一個普通物件。

 class NormalObject {
     public NormalObject() {
     }
 }

我們在這裡做的是建立一個類(物件型別)來識別我們的物件。在類的括號內是一個與類同名的單一方法(方法可以透過它們名稱末尾的括號來識別)。與類同名且沒有返回值型別的方法在 OOP 語法中稱為建構函式。要建立類的例項,程式碼不能更簡單了。

 class TestHarness {
     public static void main(String[] args) {
          NormalObject object = new NormalObject();
     }
 }

請注意,為了鼓勵這個物件的例項化,呼叫了建構函式。在這種情況下,建構函式可以在類括號之外並在另一個類定義中呼叫,因為它在上面的示例中建立建構函式時被宣告為公共訪問器。

建立單例物件

現在我們將建立單例物件。您只需要更改一件事以符合單例設計模式:使您的建構函式的訪問器私有

 class SingletonObject {
     private SingletonObject() {
     }
 }

注意建構函式的訪問器。這次它被宣告為私有。僅僅將其更改為私有,您就對您的物件型別做出了很大的改變。現在您無法例項化物件型別。嘗試一下以下程式碼。

 class TestHarness {
     public static void main(String[] args) {
          SingletonObject object = new SingletonObject();
     }
 }

程式碼返回一個錯誤,因為私有類成員無法從類本身之外的另一個類中訪問。這樣,您就停用了物件型別的例項化過程。但是,您必須找到一種獲得物件型別例項的方法。讓我們在這裡做一些更改。

 class SingletonObject {
     private static SingletonObject object;
 
     private SingletonObject() {
        // Instantiate the object.
     }
 
     public static SingletonObject getInstance() {
         if (object == null) {
             object = new SingletonObject(); // Create the object for the first and last time
         }
         return object;
     }
 }

這些更改包括新增一個名為 object 的靜態類欄位和一個 public static 方法,該方法可以透過使用類的名稱在類範圍之外訪問。要檢視如何獲得例項,讓我們編寫以下程式碼

 class TestHarness {
     public static void main(String[] args) {
          SingletonObject object = SingletonObject.getInstance();
     }
 }

這樣,您就可以控制從您的類派生的物件的建立。但是,我們還沒有揭示整個過程的最終和有趣的的部分。嘗試獲取多個物件並檢視會發生什麼。

 class TestHarness {
     public static void main(String[] args) {
          SingletonObject object1 = SingletonObject.getInstance();
          SingletonObject object2 = SingletonObject.getInstance();
     }
 }

與普通物件型別的多個例項不同,單例的多個例項實際上都是同一個物件例項。為了驗證 Java 中的概念,我們嘗試

 System.out.println(object1.equals(object2)); // output: true

程式碼返回 true,因為兩個物件宣告實際上都引用了同一個物件。因此,總結整個概念,單例可以定義為一個不能例項化多次的物件。通常,它是使用靜態自定義實現獲得的。

單例模式 & 多執行緒

Java 使用多執行緒概念來執行/執行任何程式。考慮上面討論的 SingletonObject 類。在任何時間點,多個執行緒對 getInstance() 方法的呼叫可能會建立兩個 SingletonObject 例項,從而違反了建立單例模式的全部目的。為了使單例執行緒安全,我們有三個選項:1. 同步 getInstance() 方法,它將看起來像

   // Only one thread can enter and stay in this method at a time
   public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

同步方法保證該方法永遠不會在同一時間被呼叫兩次。上面程式碼的問題是“同步”很昂貴。“同步”檢查將在每次呼叫函式時發生。因此,上面的程式碼不應該是首選。2. 另一種方法是像下面這樣建立一個單例例項

 class SingletonObject {
     private volatile static SingletonObject object;
     private final static Object lockObj = new Object(); // Use for locking
 
     private SingletonObject() {
        // Exists only to avoid instantiation.
     }
 
     public static SingletonObject getInstance() {
         if (object != null) {
             return object;
         } else {
            // Start a synchronized block, only one thread can enter and stay in the block one at a time
            synchronized(lockObj) {
                if (object == null) {
                   object = new SingletonObject();
                }
            } // End of synchronized block
            return object;
         }
     }
 }

上面的程式碼將快得多;一旦建立了單例例項,就不需要同步。它只是返回相同的例項引用。3. 使用靜態初始化塊來建立例項。JVM 確保在載入類時靜態塊只執行一次。

 class SingletonObject {
     public final static SingletonObject object;
 
     static {
        ...
        object = new SingletonObject();
     }
 
     private SingletonObject() {
        // Exists only to avoid instantiation.
     }
 
     public static SingletonObject getInstance() {
         return object;
     }    
 }

例子

在 Java 中,類 java.lang.Runtime 是一個單例。它的建構函式是受保護的,您可以透過呼叫 getRuntime() 方法來獲取例項。

成本

小心這種設計模式!它會產生與程序式程式設計中的全域性變數相同的問題,因此難以除錯。

建立

它可能很昂貴。您可能必須重構類的所有例項,除非該類是新的。

維護

沒有額外的成本。

刪除

這種模式可以很容易地刪除,因為自動重構操作可以輕鬆地刪除它的存在。

建議

  • 將方法命名為 getInstance(),以向其他開發人員指示模式的使用。
  • 將這種設計模式用於凍結資料,例如配置或外部資料。您將不會遇到除錯問題。

實現

Scala 中的實現

Scala 程式語言開箱即用地支援單例物件。“object”關鍵字建立一個類,並定義該型別的單例物件。單例的宣告方式與類相同,只是“object”替換了關鍵字“class”。

object MySingleton {
  println("Creating the singleton")
  val i : Int = 0
}
Java 中的實現

使用同步的傳統簡單方法

此解決方案是執行緒安全的,不需要特殊的語言結構

public class Singleton {
  private volatile static Singleton singleton; // volatile is needed so that multiple thread can reconcile the instance
  private Singleton(){}
  public static Singleton getSingleton() { // synchronized keyword has been removed from here
    if (singleton == null) { // needed because once there is singleton available no need to aquire monitor again & again as it is costly
      synchronized(Singleton.class) {
        if (singleton == null) { // this is needed if two threads are waiting at the monitor at the time when singleton was getting instantiated
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }
}

按需初始化持有者習語

此技術儘可能地延遲,並且在所有已知的 Java 版本中都有效。它利用了關於類初始化的語言保證,因此將在所有符合 Java 標準的編譯器和虛擬機器中正常工作。當呼叫 getInstance() 時會引用巢狀類,從而使此解決方案執行緒安全,而無需特殊的語言結構。

 public class Singleton {
   // Private constructor prevents instantiation from other classes
   private Singleton() {}
   
   /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance()
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
   private static class SingletonHolder {
     private static final Singleton INSTANCE = new Singleton();
   }
   
   public static Singleton getInstance() {
     return SingletonHolder.INSTANCE;
   }
 }

列舉方法

在他的書“Effective Java”的第二版中,Joshua Bloch 聲稱“單元素列舉型別是實現單例的最佳方法”,適用於任何支援列舉的 Java。列舉的使用非常容易實現,並且在可序列化物件方面沒有缺點,這些缺點必須在其他方法中規避。

 public enum Singleton {
   INSTANCE;
 }
D 中的實現

D 程式語言中的單例模式

import std.stdio;
import std.string;
class Singleton(T) {
    private static T instance;
    public static T opCall() {
        if(instance is null) {
            instance = new T;
        }
        return instance;
    }
}
class Foo {
    public this() {
        writefln("Foo Constructor");
    }
}
void main(){
    Foo a = Singleton!(Foo)();
    Foo b = Singleton!(Foo)();
}

或以這種方式

// this class should be in a package to make private this() not visible
class Singleton {
    private static Singleton instance;
   
    public static Singleton opCall() {
        if(instance is null) {
            instance = new Singleton();
        }
        return instance;
    }
    private this() {
        writefln("Singleton constructor");
    }
}
void main(){
    Singleton a = Singleton();
    Singleton b = Singleton();
}
PHP 5 中的實現

PHP 5 中的單例模式

<?php
class Singleton {
  // object instance
  private static $instance;
  // The protected construct prevents instantiating the class externally.  The construct can be
  // empty, or it can contain additional instructions...
  // This should also be final to prevent extending objects from overriding the constructor with
  // public.
  protected final function __construct() {
    ...
  }
  // The clone and wakeup methods prevents external instantiation of copies of the Singleton class,
  // thus eliminating the possibility of duplicate objects.  The methods can be empty, or
  // can contain additional code (most probably generating error messages in response
  // to attempts to call).
  public function __clone() {
    trigger_error('Clone is not allowed.', E_USER_ERROR);
  }
  public function __wakeup() {
    trigger_error('Deserializing is not allowed.', E_USER_ERROR);
  }
  // This method must be static, and must return an instance of the object if the object
  // does not already exist.
  public static function getInstance() {
    if (!self::$instance instanceof self) {
      self::$instance = new self;
    }
    return self::$instance;
  }
  // One or more public methods that grant access to the Singleton object, and its private
  // methods and properties via accessor methods.
  public function doAction() {
    ...
  }
}
// usage
Singleton::getInstance()->doAction();
?>
ActionScript 3.0 中的實現

ActionScript 3.0 中沒有私有建構函式 - 這阻止了使用 ActionScript 2.0 方法來實現單例模式。許多不同的 AS3 單例實現已在網路上釋出。

package {
public class Singleton  {
private static var _instance:Singleton = new Singleton();
public function Singleton () {
           if (_instance){
       throw new Error(
                            "Singleton can only be accessed through Singleton.getInstance()"
                        );
                    }
}
public static function getInstance():Singleton {
return _instance;
}
}
}
Objective-C 中的實現

在 Objective-C 中實現單例的一種常見方法如下

@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;
 
  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];
   
    return sharedSingleton;
  }
}
@end

如果不需要執行緒安全性,可以省略同步,使 +sharedSingleton 方法如下

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;
  if (!sharedSingleton)
    sharedSingleton = [[MySingleton alloc] init];
  return sharedSingleton;
}

這種模式在 Cocoa 框架中被廣泛使用(例如,NSApplicationNSColorPanelNSFontPanelNSWorkspace,僅舉幾例)。有些人可能會爭辯說,這嚴格來說不是單例,因為可以分配多個物件的例項。解決這個問題的一種常見方法是使用斷言或異常來阻止這種雙重分配。

@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
static MySingleton *sharedSingleton;
+ (MySingleton *)sharedSingleton
{
  @synchronized(self)
  {
    if (!sharedSingleton)
      [[MySingleton alloc] init];
   
    return sharedSingleton;
  }
}
+(id)alloc
{
  @synchronized(self)
  {
    NSAssert(sharedSingleton == nil, @"Attempted to allocate a second instance of a singleton.");
    sharedSingleton = [super alloc];
    return sharedSingleton;
  }
}
@end

在 Objective-C 中表達單例模式還有其他方法,但它們並不總是那麼簡單或容易理解,尤其是因為它們可能依賴於 -init 方法返回的物件不是 self。已知一些 Cocoa “類叢集”(例如 NSStringNSNumber)表現出這種行為。請注意,@synchronized 在某些 Objective-C 配置中不可用,因為它依賴於 NeXT/Apple 執行時。它也相對較慢,因為它必須根據括號中的物件查詢鎖。

C# 中的實現

最簡單的是

public class Singleton
{
    // The combination of static and readonly makes the instantiation
    // thread safe.  Plus the constructor being protected (it can be
    // private as well), makes the class sure to not have any other
    // way to instantiate this class than using this member variable.
    public static readonly Singleton Instance = new Singleton();
    // Protected constructor is sufficient to avoid other instantiation
    // This must be present otherwise the compiler provides a default
    // public constructor
    //
    protected Singleton()
    {
    }
}

此示例是執行緒安全的,並且具有延遲初始化功能。

/// Class implements singleton pattern.
public class Singleton
{
    //Use Lazy<T> to lazily initialize the class and provide thread-safe access
    private static readonly Lazy<Singleton> _lazyInstance = new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance 
    { 
        get { return _lazyInstance.Value; } 
    }
        
    private Singleton() { }
}

C# 2.0 中的示例 (執行緒安全且延遲初始化) 注意:這不是推薦的實現方式,因為 “TestClass” 有一個預設的公共建構函式,這違反了單例的定義。一個真正的單例不應該被例項化超過一次。關於 C# 中泛型單例解決方案的更多資訊:http://www.c-sharpcorner.com/UploadFile/snorrebaard/GenericSingleton11172008110419AM/GenericSingleton.aspx

/// Parent for singleton
/// <typeparam name="T">Singleton class</typeparam>
  public class Singleton<T> where T : class, new()
  {
    protected Singleton() { }
    private sealed class SingletonCreator<S> where S : class, new()
    {
      private static readonly S instance = new S();
      //explicit static constructor to disable beforefieldinit      
      static SingletonCreator() { }
      public static S CreatorInstance
      {
        get { return instance; }
      }
    }
    public static T Instance
    {
      get { return SingletonCreator<T>.CreatorInstance; }
    }
  }
/// Concrete Singleton
public class TestClass : Singleton<TestClass>
{
    public string TestProc()
    {
        return "Hello World";
    }
}
// Somewhere in the code
.....
TestClass.Instance.TestProc();
.....
Delphi 中的實現

正如 James Heyworth 在 1996 年 11 月 11 日提交給堪培拉 PC 使用者組 Delphi SIG 的一篇論文中所述,Delphi 視覺化元件庫中內建了單例模式的幾個示例。本單元演示了用於建立單例元件和單例物件的技巧。

unit Singletn;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;
type
  TCSingleton = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;
  TOSingleton = class(TObject)
  public
    constructor Create;
    destructor Destroy; override;
  end;
var
  Global_CSingleton: TCSingleton;
  Global_OSingleton: TOSingleton;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Design Patterns', [TCSingleton]);
end;
{ TCSingleton }
constructor TCSingleton.Create(AOwner: TComponent);
begin
  if Global_CSingleton <> nil then
    {NB could show a message or raise a different exception here}
    Abort
  else begin
    inherited Create(AOwner);
    Global_CSingleton := Self;
  end;
end;
destructor TCSingleton.Destroy;
begin
  if Global_CSingleton = Self then
    Global_CSingleton := nil;
  inherited Destroy;
end;
{ TOSingleton }
constructor TOSingleton.Create;
begin
if Global_OSingleton <> nil then
    {NB could show a message or raise a different exception here}
    Abort
  else
    Global_OSingleton := Self;
end;
destructor TOSingleton.Destroy;
begin
  if Global_OSingleton = Self then
    Global_OSingleton := nil;
  inherited Destroy;
end;
procedure FreeGlobalObjects; far;
begin
  if Global_CSingleton <> nil then
    Global_CSingleton.Free;
  if Global_OSingleton <> nil then
    Global_OSingleton.Free;
end;
begin
  AddExitProc(FreeGlobalObjects);
end.
Python 中的實現

單例模式的所需屬性可以用 Python 中定義的模組最簡單地封裝,該模組包含模組級變數和函式。要使用此模組化單例,客戶端程式碼只需匯入模組以按正常方式訪問其屬性和函式。這避開了下面顯式編碼版本中的許多問題,並且具有不需要任何程式碼來實現的獨特優勢。根據有影響力的 Python 程式設計師 Alex Martelli 的說法,單例設計模式 (DP) 有一個朗朗上口的名稱,但關注點錯誤——關注身份而不是狀態。Borg 設計模式讓所有例項共享狀態而不是快取建立相同例項。在 Python 社群中,一個粗略的共識是,在例項之間共享狀態比在類初始化時快取相同例項的建立更優雅,至少在 Python 中是這樣。編碼共享狀態幾乎是透明的。

class Borg:
   __shared_state = {}
   def __init__(self):
       self.__dict__ = self.__shared_state
   # and whatever else is needed in the class -- that's all!

但使用新的類風格,這是一個更好的解決方案,因為只建立了一個例項。

class Singleton (object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'self'):
            cls.self = object.__new__(cls)
        return cls.self
#Usage
mySingleton1 = Singleton()
mySingleton2 = Singleton()
 
#mySingleton1 and  mySingleton2 are the same instance.
assert mySingleton1 is mySingleton2

兩個注意事項

  • 除非 cls.__init__ 設定為空函式,否則每次呼叫 Singleton() 時都會呼叫 __init__ 方法。
  • 如果需要從 Singleton 類繼承,instance 可能應該是顯式屬於 Singleton 類的 dictionary
class  InheritableSingleton (object):
    instances = {}
    def __new__(cls, *args, **kwargs):
        if InheritableSingleton.instances.get(cls) is None:
            cls.__original_init__ = cls.__init__
            InheritableSingleton.instances[cls] = object.__new__(cls, *args, **kwargs)
        elif cls.__init__ == cls.__original_init__:
            def nothing(*args, **kwargs):
                pass
            cls.__init__ = nothing
        return InheritableSingleton.instances[cls]

要建立一個從非單例繼承的單例,必須使用多重繼承。

class  Singleton (NonSingletonClass, object):
    instance = None      
    def __new__(cls, *args, **kargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls, *args, **kargs)
        return cls.instance

務必從 Singleton 的 __init__ 函式中呼叫 NonSingletonClass 的 __init__ 函式。還建議使用元類進行更優雅的方法。

class SingletonType(type):
    def __call__(cls):
        if getattr(cls, '__instance__', None) is None:
            instance = cls.__new__(cls)
            instance.__init__()
            cls.__instance__ = instance
        return cls.__instance__
# Usage
class Singleton(object):
    __metaclass__ = SingletonType
    def __init__(self):
        print '__init__:', self
class OtherSingleton(object):
    __metaclass__ = SingletonType
    def __init__(self):
        print 'OtherSingleton __init__:', self
# Tests
s1 = Singleton()
s2 = Singleton()
assert s1
assert s2
assert s1 is s2
os1 = OtherSingleton()
os2 = OtherSingleton()
assert os1
assert os2
assert os1 is os2
Perl 中的實現

在 Perl 5.10 或更高版本中,可以使用狀態變數。

package MySingletonClass;
use strict;
use warnings;
use 5.010;
sub new {
    my ($class) = @_;
    state $the_instance;
    if (! defined $the_instance) {
        $the_instance = bless { }, $class;
    }
    return $the_instance;
}

在較舊的 Perl 中,只需使用全域性變數。

package MySingletonClass;
use strict;
use warnings;
my $THE_INSTANCE;
sub new {
    my ($class) = @_;
    if (! defined $THE_INSTANCE) {
        $THE_INSTANCE = bless { }, $class;
    }
    return $THE_INSTANCE;
}

如果使用 Moose,則有 MooseX::Singleton 擴充套件模組。

Ruby 中的實現

在 Ruby 中,只需將標準庫中的 Singleton 模組包含到類中。

require 'singleton'
class Example
  include Singleton
end
# Access to the instance:
Example.instance
ABAP 物件中的實現

在 ABAP 物件中,要使例項化私有,請在類中新增一個型別為 ref 的屬性,並新增一個靜態方法來控制例項化。

program pattern_singleton.

***********************************************************************

   * Singleton
   * =========
   * Intent

*

   * Ensure a class has only one instance, and provide a global point
   * of access to it.

***********************************************************************

class lcl_Singleton definition create private.

  public section.

  class-methods:
    get_Instance returning value(Result) type ref to lcl_Singleton.

  private section.
    class-data:
      fg_Singleton type ref to lcl_Singleton.

endclass.

class lcl_Singleton implementation.

  method get_Instance.
    if ( fg_Singleton is initial ).
      create object fg_Singleton.
    endif.
    Result = fg_Singleton.
  endmethod.

endclass.


Clipboard

待辦事項
新增更多示例。


代理 計算機科學設計模式
單例模式
狀態


您對本頁面有疑問嗎?
在此處提問


在本手冊中建立一個新頁面


華夏公益教科書