跳转到内容

单例模式

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

待办事项
添加更多示例。


代理 计算机科学设计模式
单例模式
状态


您对本页面有疑问吗?
在此处提问


在本手册中创建一个新页面


华夏公益教科书