https://refactoringguru.cn/design-patterns/singleton

创建型模式-单例模式

亦称: 单件模式、Singleton

意图

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

问题

单例模式同时解决了两个问题, 所以违反了_单一职责原则_:

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

    客户端甚至可能没有意识到它们一直都在使用同一个对象

  2. 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

    还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例

解决方案

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

真实世界类比

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

单例模式结构

  1. 单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。

    单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。

伪代码

在本例中, 数据库连接类即是一个单例

该类不提供公有构造函数, 因此获取该对象的唯一方式是调用 获取实例方法。 该方法将缓存首次生成的对象, 并为所有后续调用返回该对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处
// 都能访问相同的数据库连接实例。
class Database is
// 保存单例实例的成员变量必须被声明为静态类型。
private static field instance: Database

// 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构
// 造方法。
private constructor Database() is
// 部分初始化代码(例如到数据库服务器的实际连接)。
// ...

// 用于控制对单例实例的访问权限的静态方法。
public static method getInstance() is
if (Database.instance == null) then
acquireThreadLock() and then
// 确保在该线程等待解锁时,其他线程没有初始化该实例。
if (Database.instance == null) then
Database.instance = new Database()
return Database.instance

// 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。
public method query(sql) is
// 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以
// 在这里添加限流或缓冲逻辑。
// ...

class Application is
method main() is
Database foo = Database.getInstance()
foo.query("SELECT ...")
// ...
Database bar = Database.getInstance()
bar.query("SELECT ...")
// 变量 `bar` 和 `foo` 中将包含同一个对象。

单例模式适合应用场景

如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。

单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

如果你需要更加严格地控制全局变量,可以使用单例模式。

单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

单例模式优缺点

优点

  • 你可以保证一个类只有一个实例。
  • 你获得了一个指向该实例的全局访问节点。
  • 仅在首次请求单例对象时对其进行初始化。

缺点

  • 违反了_单一职责原则_。 该模式同时解决了两个问题。
  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

与其他模式的关系

  • 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。
  • 抽象工厂模式生成器模式原型模式都可以用单例来实现。

代码示例

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。

在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。

在 C# 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 C# 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

Program.cs: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;

namespace Singleton
{
// The Singleton class defines the `GetInstance` method that serves as an
// alternative to constructor and lets clients access the same instance of
// this class over and over.
class Singleton
{
// The Singleton's constructor should always be private to prevent
// direct construction calls with the `new` operator.
private Singleton() { }

// The Singleton's instance is stored in a static field. There there are
// multiple ways to initialize this field, all of them have various pros
// and cons. In this example we'll show the simplest of these ways,
// which, however, doesn't work really well in multithreaded program.
private static Singleton _instance;

// This is the static method that controls the access to the singleton
// instance. On the first run, it creates a singleton object and places
// it into the static field. On subsequent runs, it returns the client
// existing object stored in the static field.
public static Singleton GetInstance()
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}

// Finally, any singleton should define some business logic, which can
// be executed on its instance.
public static void someBusinessLogic()
{
// ...
}
}

class Program
{
static void Main(string[] args)
{
// The client code.
Singleton s1 = Singleton.GetInstance();
Singleton s2 = Singleton.GetInstance();

if (s1 == s2)
{
Console.WriteLine("Singleton works, both variables contain the same instance.");
}
else
{
Console.WriteLine("Singleton failed, variables contain different instances.");
}
}
}
}

Output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

Program.cs: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System;
using System.Threading;

namespace Singleton
{
// This Singleton implementation is called "double check lock". It is safe
// in multithreaded environment and provides lazy initialization for the
// Singleton object.
class Singleton
{
private Singleton() { }

private static Singleton _instance;

// We now have a lock object that will be used to synchronize threads
// during first access to the Singleton.
private static readonly object _lock = new object();

public static Singleton GetInstance(string value)
{
// This conditional is needed to prevent threads stumbling over the
// lock once the instance is ready.
if (_instance == null)
{
// Now, imagine that the program has just been launched. Since
// there's no Singleton instance yet, multiple threads can
// simultaneously pass the previous conditional and reach this
// point almost at the same time. The first of them will acquire
// lock and will proceed further, while the rest will wait here.
lock (_lock)
{
// The first thread to acquire the lock, reaches this
// conditional, goes inside and creates the Singleton
// instance. Once it leaves the lock block, a thread that
// might have been waiting for the lock release may then
// enter this section. But since the Singleton field is
// already initialized, the thread won't create a new
// object.
if (_instance == null)
{
_instance = new Singleton();
_instance.Value = value;
}
}
}
return _instance;
}

// We'll use this property to prove that our Singleton really works.
public string Value { get; set; }
}

class Program
{
static void Main(string[] args)
{
// The client code.

Console.WriteLine(
"{0}\n{1}\n\n{2}\n",
"If you see the same value, then singleton was reused (yay!)",
"If you see different values, then 2 singletons were created (booo!!)",
"RESULT:"
);

Thread process1 = new Thread(() =>
{
TestSingleton("FOO");
});
Thread process2 = new Thread(() =>
{
TestSingleton("BAR");
});

process1.Start();
process2.Start();

process1.Join();
process2.Join();
}

public static void TestSingleton(string value)
{
Singleton singleton = Singleton.GetInstance(value);
Console.WriteLine(singleton.Value);
}
}
}

Output.txt: 执行结果

1
2
FOO
FOO

希望了解更多?

C# 中还有更多特殊类型的单例模式。 阅读这篇文章以了解更多:

深入理解 C#: 单例的实现

在 C++ 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 C++ 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

main.cc: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* The Singleton class defines the `GetInstance` method that serves as an
* alternative to constructor and lets clients access the same instance of this
* class over and over.
*/
class Singleton
{

/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/

protected:
Singleton(const std::string value): value_(value)
{
}

static Singleton* singleton_;

std::string value_;

public:

/**
* Singletons should not be cloneable.
*/
Singleton(Singleton &other) = delete;
/**
* Singletons should not be assignable.
*/
void operator=(const Singleton &) = delete;
/**
* This is the static method that controls the access to the singleton
* instance. On the first run, it creates a singleton object and places it
* into the static field. On subsequent runs, it returns the client existing
* object stored in the static field.
*/

static Singleton *GetInstance(const std::string& value);
/**
* Finally, any singleton should define some business logic, which can be
* executed on its instance.
*/
void SomeBusinessLogic()
{
// ...
}

std::string value() const{
return value_;
}
};

Singleton* Singleton::singleton_= nullptr;;

/**
* Static methods should be defined outside the class.
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
/**
* This is a safer way to create an instance. instance = new Singleton is
* dangeruous in case two instance threads wants to access at the same time
*/
if(singleton_==nullptr){
singleton_ = new Singleton(value);
}
return singleton_;
}

void ThreadFoo(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}

void ThreadBar(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}


int main()
{
std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
"If you see different values, then 2 singletons were created (booo!!)\n\n" <<
"RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();

return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)

RESULT:
BAR
FOO

线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

main.cc: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* The Singleton class defines the `GetInstance` method that serves as an
* alternative to constructor and lets clients access the same instance of this
* class over and over.
*/
class Singleton
{

/**
* The Singleton's constructor/destructor should always be private to
* prevent direct construction/desctruction calls with the `new`/`delete`
* operator.
*/
private:
static Singleton * pinstance_;
static std::mutex mutex_;

protected:
Singleton(const std::string value): value_(value)
{
}
~Singleton() {}
std::string value_;

public:
/**
* Singletons should not be cloneable.
*/
Singleton(Singleton &other) = delete;
/**
* Singletons should not be assignable.
*/
void operator=(const Singleton &) = delete;
/**
* This is the static method that controls the access to the singleton
* instance. On the first run, it creates a singleton object and places it
* into the static field. On subsequent runs, it returns the client existing
* object stored in the static field.
*/

static Singleton *GetInstance(const std::string& value);
/**
* Finally, any singleton should define some business logic, which can be
* executed on its instance.
*/
void SomeBusinessLogic()
{
// ...
}

std::string value() const{
return value_;
}
};

/**
* Static methods should be defined outside the class.
*/

Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;

/**
* The first time we call GetInstance we will lock the storage location
* and then we make sure again that the variable is null and then we
* set the value. RU:
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton(value);
}
return pinstance_;
}

void ThreadFoo(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}

void ThreadBar(){
// Following code emulates slow initialization.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}

int main()
{
std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
"If you see different values, then 2 singletons were created (booo!!)\n\n" <<
"RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();

return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)

RESULT:
FOO
FOO

在 Java 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 Java 代码中的使用频率正在逐步减少。

尽管如此, Java 核心程序库中仍有相当多的单例示例:

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例(单线程)

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

Singleton.java: 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package refactoring_guru.singleton.example.non_thread_safe;

public final class Singleton {
private static Singleton instance;
public String value;

private Singleton(String value) {
// The following code emulates slow initialization.
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
this.value = value;
}

public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}

DemoSingleThread.java: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
package refactoring_guru.singleton.example.non_thread_safe;

public class DemoSingleThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
"RESULT:" + "\n");
Singleton singleton = Singleton.getInstance("FOO");
Singleton anotherSingleton = Singleton.getInstance("BAR");
System.out.println(singleton.value);
System.out.println(anotherSingleton.value);
}
}

OutputDemoSingleThread.txt: 执行结果

1
2
3
4
5
6
7
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

基础单例(多线程)

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

Singleton.java: 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package refactoring_guru.singleton.example.non_thread_safe;

public final class Singleton {
private static Singleton instance;
public String value;

private Singleton(String value) {
// The following code emulates slow initialization.
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
this.value = value;
}

public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}

DemoMultiThread.java: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package refactoring_guru.singleton.example.non_thread_safe;

public class DemoMultiThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
"RESULT:" + "\n");
Thread threadFoo = new Thread(new ThreadFoo());
Thread threadBar = new Thread(new ThreadBar());
threadFoo.start();
threadBar.start();
}

static class ThreadFoo implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("FOO");
System.out.println(singleton.value);
}
}

static class ThreadBar implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("BAR");
System.out.println(singleton.value);
}
}
}

OutputDemoMultiThread.txt: 执行结果

1
2
3
4
5
6
7
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
BAR

采用延迟加载的线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

Singleton.java: 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package refactoring_guru.singleton.example.thread_safe;

public final class Singleton {
// The field must be declared volatile so that double check lock would work
// correctly.
private static volatile Singleton instance;

public String value;

private Singleton(String value) {
this.value = value;
}

public static Singleton getInstance(String value) {
// The approach taken here is called double-checked locking (DCL). It
// exists to prevent race condition between multiple threads that may
// attempt to get singleton instance at the same time, creating separate
// instances as a result.
//
// It may seem that having the `result` variable here is completely
// pointless. There is, however, a very important caveat when
// implementing double-checked locking in Java, which is solved by
// introducing this local variable.
//
// You can read more info DCL issues in Java here:
// https://refactoring.guru/java-dcl-issue
Singleton result = instance;
if (result != null) {
return result;
}
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
}

DemoMultiThread.java: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package refactoring_guru.singleton.example.thread_safe;

public class DemoMultiThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\n\n" +
"RESULT:" + "\n");
Thread threadFoo = new Thread(new ThreadFoo());
Thread threadBar = new Thread(new ThreadBar());
threadFoo.start();
threadBar.start();
}

static class ThreadFoo implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("FOO");
System.out.println(singleton.value);
}
}

static class ThreadBar implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("BAR");
System.out.println(singleton.value);
}
}
}

OutputDemoMultiThread.txt: 执行结果

1
2
3
4
5
6
7
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

BAR
BAR

希望了解更多?

Java 中还有更多特殊类型的单例模式。 阅读这篇文章以了解更多:

Java 单例设计模式的最佳实践与示例

在 PHP 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 PHP 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

概念示例

本例说明了单例设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。

index.php: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php

namespace RefactoringGuru\Singleton\Conceptual;

/**
* The Singleton class defines the `GetInstance` method that serves as an
* alternative to constructor and lets clients access the same instance of this
* class over and over.
*/
class Singleton
{
/**
* The Singleton's instance is stored in a static field. This field is an
* array, because we'll allow our Singleton to have subclasses. Each item in
* this array will be an instance of a specific Singleton's subclass. You'll
* see how this works in a moment.
*/
private static $instances = [];

/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
protected function __construct() { }

/**
* Singletons should not be cloneable.
*/
protected function __clone() { }

/**
* Singletons should not be restorable from strings.
*/
public function __wakeup()
{
throw new \Exception("Cannot unserialize a singleton.");
}

/**
* This is the static method that controls the access to the singleton
* instance. On the first run, it creates a singleton object and places it
* into the static field. On subsequent runs, it returns the client existing
* object stored in the static field.
*
* This implementation lets you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static function getInstance(): Singleton
{
$cls = static::class;
if (!isset(self::$instances[$cls])) {
self::$instances[$cls] = new static();
}

return self::$instances[$cls];
}

/**
* Finally, any singleton should define some business logic, which can be
* executed on its instance.
*/
public function someBusinessLogic()
{
// ...
}
}

/**
* The client code.
*/
function clientCode()
{
$s1 = Singleton::getInstance();
$s2 = Singleton::getInstance();
if ($s1 === $s2) {
echo "Singleton works, both variables contain the same instance.";
} else {
echo "Singleton failed, variables contain different instances.";
}
}

clientCode();

Output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

真实世界示例

单例模式由于限制了代码复用, 且让单元测试复杂化而名声不佳。 但它在有些情况下仍然非常实用, 特别是在需要控制一些共享资源时十分方便。 例如, 全局日志对象必须对日志文件的访问权限进行控制。 另一个例子: 共享的运行时配置存储。

index.php: 真实世界示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<?php

namespace RefactoringGuru\Singleton\RealWorld;

/**
* If you need to support several types of Singletons in your app, you can
* define the basic features of the Singleton in a base class, while moving the
* actual business logic (like logging) to subclasses.
*/
class Singleton
{
/**
* The actual singleton's instance almost always resides inside a static
* field. In this case, the static field is an array, where each subclass of
* the Singleton stores its own instance.
*/
private static $instances = [];

/**
* Singleton's constructor should not be public. However, it can't be
* private either if we want to allow subclassing.
*/
protected function __construct() { }

/**
* Cloning and unserialization are not permitted for singletons.
*/
protected function __clone() { }

public function __wakeup()
{
throw new \Exception("Cannot unserialize singleton");
}

/**
* The method you use to get the Singleton's instance.
*/
public static function getInstance()
{
$subclass = static::class;
if (!isset(self::$instances[$subclass])) {
// Note that here we use the "static" keyword instead of the actual
// class name. In this context, the "static" keyword means "the name
// of the current class". That detail is important because when the
// method is called on the subclass, we want an instance of that
// subclass to be created here.

self::$instances[$subclass] = new static();
}
return self::$instances[$subclass];
}
}

/**
* The logging class is the most known and praised use of the Singleton pattern.
* In most cases, you need a single logging object that writes to a single log
* file (control over shared resource). You also need a convenient way to access
* that instance from any context of your app (global access point).
*/
class Logger extends Singleton
{
/**
* A file pointer resource of the log file.
*/
private $fileHandle;

/**
* Since the Singleton's constructor is called only once, just a single file
* resource is opened at all times.
*
* Note, for the sake of simplicity, we open the console stream instead of
* the actual file here.
*/
protected function __construct()
{
$this->fileHandle = fopen('php://stdout', 'w');
}

/**
* Write a log entry to the opened file resource.
*/
public function writeLog(string $message): void
{
$date = date('Y-m-d');
fwrite($this->fileHandle, "$date: $message\n");
}

/**
* Just a handy shortcut to reduce the amount of code needed to log messages
* from the client code.
*/
public static function log(string $message): void
{
$logger = static::getInstance();
$logger->writeLog($message);
}
}

/**
* Applying the Singleton pattern to the configuration storage is also a common
* practice. Often you need to access application configurations from a lot of
* different places of the program. Singleton gives you that comfort.
*/
class Config extends Singleton
{
private $hashmap = [];

public function getValue(string $key): string
{
return $this->hashmap[$key];
}

public function setValue(string $key, string $value): void
{
$this->hashmap[$key] = $value;
}
}

/**
* The client code.
*/
Logger::log("Started!");

// Compare values of Logger singleton.
$l1 = Logger::getInstance();
$l2 = Logger::getInstance();
if ($l1 === $l2) {
Logger::log("Logger has a single instance.");
} else {
Logger::log("Loggers are different.");
}

// Check how Config singleton saves data...
$config1 = Config::getInstance();
$login = "test_login";
$password = "test_password";
$config1->setValue("login", $login);
$config1->setValue("password", $password);
// ...and restores it.
$config2 = Config::getInstance();
if ($login == $config2->getValue("login") &&
$password == $config2->getValue("password")
) {
Logger::log("Config singleton also works fine.");
}

Logger::log("Finished!");

Output.txt: 执行结果

1
2
3
4
2018-06-04: Started!
2018-06-04: Logger has a single instance.
2018-06-04: Config singleton also works fine.
2018-06-04: Finished!

在 Python 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 Python 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

main.py: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class SingletonMeta(type):
"""
The Singleton class can be implemented in different ways in Python. Some
possible methods include: base class, decorator, metaclass. We will use the
metaclass because it is best suited for this purpose.
"""

_instances = {}

def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""

# ...


if __name__ == "__main__":
# The client code.

s1 = Singleton()
s2 = Singleton()

if id(s1) == id(s2):
print("Singleton works, both variables contain the same instance.")
else:
print("Singleton failed, variables contain different instances.")

Output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

main.py: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from threading import Lock, Thread

class SingletonMeta(type):
"""
This is a thread-safe implementation of Singleton.
"""

_instances = {}

_lock: Lock = Lock()
"""
We now have a lock object that will be used to synchronize threads during
first access to the Singleton.
"""

def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
# Now, imagine that the program has just been launched. Since there's no
# Singleton instance yet, multiple threads can simultaneously pass the
# previous conditional and reach this point almost at the same time. The
# first of them will acquire lock and will proceed further, while the
# rest will wait here.
with cls._lock:
# The first thread to acquire the lock, reaches this conditional,
# goes inside and creates the Singleton instance. Once it leaves the
# lock block, a thread that might have been waiting for the lock
# release may then enter this section. But since the Singleton field
# is already initialized, the thread won't create a new object.
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
value: str = None
"""
We'll use this property to prove that our Singleton really works.
"""

def __init__(self, value: str) -> None:
self.value = value

def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""


def test_singleton(value: str) -> None:
singleton = Singleton(value)
print(singleton.value)


if __name__ == "__main__":
# The client code.

print("If you see the same value, then singleton was reused (yay!)\n"
"If you see different values, "
"then 2 singletons were created (booo!!)\n\n"
"RESULT:\n")

process1 = Thread(target=test_singleton, args=("FOO",))
process2 = Thread(target=test_singleton, args=("BAR",))
process1.start()
process2.start()

Output.txt: 执行结果

1
2
3
4
5
6
7
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

在 Ruby 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 Ruby 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

main.rb: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# The Singleton class defines the `instance` method that lets clients access the
# unique singleton instance.
class Singleton
@instance = new

private_class_method :new

# The static method that controls the access to the singleton instance.
#
# This implementation let you subclass the Singleton class while keeping just
# one instance of each subclass around.
def self.instance
@instance
end

# Finally, any singleton should define some business logic, which can be
# executed on its instance.
def some_business_logic
# ...
end
end

# The client code.

s1 = Singleton.instance
s2 = Singleton.instance

if s1.equal?(s2)
print 'Singleton works, both variables contain the same instance.'
else
print 'Singleton failed, variables contain different instances.'
end

output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

main.rb: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# The Singleton class defines the `intance` method that lets clients access the
# unique singleton instance.
class Singleton
attr_reader :value

@instance_mutex = Mutex.new

private_class_method :new

def initialize(value)
@value = value
end

# The static method that controls the access to the singleton instance.
#
# This implementation let you subclass the Singleton class while keeping just
# one instance of each subclass around.
def self.instance(value)
return @instance if @instance

@instance_mutex.synchronize do
@instance ||= new(value)
end

@instance
end

# Finally, any singleton should define some business logic, which can be
# executed on its instance.
def some_business_logic
# ...
end
end

# @param [String] value
def test_singleton(value)
singleton = Singleton.instance(value)
puts singleton.value
end

# The client code.

puts "If you see the same value, then singleton was reused (yay!)\n"\
"If you see different values, then 2 singletons were created (booo!!)\n\n"\
"RESULT:\n\n"

process1 = Thread.new { test_singleton('FOO') }
process2 = Thread.new { test_singleton('BAR') }
process1.join
process2.join

output.txt: 执行结果

1
2
3
4
5
6
7
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

在 Swift 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 Swift 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

概念示例

本例说明了单例设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。

Example.swift: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import XCTest

/// The Singleton class defines the `shared` field that lets clients access the
/// unique singleton instance.
class Singleton {

/// The static field that controls the access to the singleton instance.
///
/// This implementation let you extend the Singleton class while keeping
/// just one instance of each subclass around.
static var shared: Singleton = {
let instance = Singleton()
// ... configure the instance
// ...
return instance
}()

/// The Singleton's initializer should always be private to prevent direct
/// construction calls with the `new` operator.
private init() {}

/// Finally, any singleton should define some business logic, which can be
/// executed on its instance.
func someBusinessLogic() -> String {
// ...
return "Result of the 'someBusinessLogic' call"
}
}

/// Singletons should not be cloneable.
extension Singleton: NSCopying {

func copy(with zone: NSZone? = nil) -> Any {
return self
}
}

/// The client code.
class Client {
// ...
static func someClientCode() {
let instance1 = Singleton.shared
let instance2 = Singleton.shared

if (instance1 === instance2) {
print("Singleton works, both variables contain the same instance.")
} else {
print("Singleton failed, variables contain different instances.")
}
}
// ...
}

/// Let's see how it all works together.
class SingletonConceptual: XCTestCase {

func testSingletonConceptual() {
Client.someClientCode()
}
}

Output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

真实世界示例

Example.swift: 真实世界示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import XCTest

/// Singleton Design Pattern
///
/// Intent: Ensure that class has a single instance, and provide a global point
/// of access to it.

class SingletonRealWorld: XCTestCase {

func testSingletonRealWorld() {

/// There are two view controllers.
///
/// MessagesListVC displays a list of last messages from a user's chats.
/// ChatVC displays a chat with a friend.
///
/// FriendsChatService fetches messages from a server and provides all
/// subscribers (view controllers in our example) with new and removed
/// messages.
///
/// FriendsChatService is used by both view controllers. It can be
/// implemented as an instance of a class as well as a global variable.
///
/// In this example, it is important to have only one instance that
/// performs resource-intensive work.

let listVC = MessagesListVC()
let chatVC = ChatVC()

listVC.startReceiveMessages()
chatVC.startReceiveMessages()

/// ... add view controllers to the navigation stack ...
}
}


class BaseVC: UIViewController, MessageSubscriber {

func accept(new messages: [Message]) {
/// handle new messages in the base class
}

func accept(removed messages: [Message]) {
/// handle removed messages in the base class
}

func startReceiveMessages() {

/// The singleton can be injected as a dependency. However, from an
/// informational perspective, this example calls FriendsChatService
/// directly to illustrate the intent of the pattern, which is: "...to
/// provide the global point of access to the instance..."

FriendsChatService.shared.add(subscriber: self)
}
}

class MessagesListVC: BaseVC {

override func accept(new messages: [Message]) {
print("MessagesListVC accepted 'new messages'")
/// handle new messages in the child class
}

override func accept(removed messages: [Message]) {
print("MessagesListVC accepted 'removed messages'")
/// handle removed messages in the child class
}

override func startReceiveMessages() {
print("MessagesListVC starts receive messages")
super.startReceiveMessages()
}
}

class ChatVC: BaseVC {

override func accept(new messages: [Message]) {
print("ChatVC accepted 'new messages'")
/// handle new messages in the child class
}

override func accept(removed messages: [Message]) {
print("ChatVC accepted 'removed messages'")
/// handle removed messages in the child class
}

override func startReceiveMessages() {
print("ChatVC starts receive messages")
super.startReceiveMessages()
}
}

/// Protocol for call-back events

protocol MessageSubscriber {

func accept(new messages: [Message])
func accept(removed messages: [Message])
}

/// Protocol for communication with a message service

protocol MessageService {

func add(subscriber: MessageSubscriber)
}

/// Message domain model

struct Message {

let id: Int
let text: String
}


class FriendsChatService: MessageService {

static let shared = FriendsChatService()

private var subscribers = [MessageSubscriber]()

func add(subscriber: MessageSubscriber) {

/// In this example, fetching starts again by adding a new subscriber
subscribers.append(subscriber)

/// Please note, the first subscriber will receive messages again when
/// the second subscriber is added
startFetching()
}

func startFetching() {

/// Set up the network stack, establish a connection...
/// ...and retrieve data from a server

let newMessages = [Message(id: 0, text: "Text0"),
Message(id: 5, text: "Text5"),
Message(id: 10, text: "Text10")]

let removedMessages = [Message(id: 1, text: "Text0")]

/// Send updated data to subscribers
receivedNew(messages: newMessages)
receivedRemoved(messages: removedMessages)
}
}

private extension FriendsChatService {

func receivedNew(messages: [Message]) {

subscribers.forEach { item in
item.accept(new: messages)
}
}

func receivedRemoved(messages: [Message]) {

subscribers.forEach { item in
item.accept(removed: messages)
}
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
MessagesListVC starts receive messages
MessagesListVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
======== At this point, the second subscriber is added ======
ChatVC starts receive messages
MessagesListVC accepted 'new messages'
ChatVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
ChatVC accepted 'removed messages'

在 TypeScript 中使用模式

复杂度: ★☆☆

流行度: ★★☆

使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 TypeScript 代码中的使用频率正在逐步减少。

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

概念示例

本例说明了单例设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

index.ts: 概念示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* The Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
class Singleton {
private static instance: Singleton;

/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() { }

/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}

return Singleton.instance;
}

/**
* Finally, any singleton should define some business logic, which can be
* executed on its instance.
*/
public someBusinessLogic() {
// ...
}
}

/**
* The client code.
*/
function clientCode() {
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

if (s1 === s2) {
console.log('Singleton works, both variables contain the same instance.');
} else {
console.log('Singleton failed, variables contain different instances.');
}
}

clientCode();

Output.txt: 执行结果

1
Singleton works, both variables contain the same instance.

概念示例

通常而言, 单例实例会在结构体首次初始化时创建。 为了实现这一操作, 我们在结构体中定义一个 get­Instance获取实例方法。 该方法将负责创建和返回单例实例。 创建后, 每次调用 get­Instance时都会返回相同的单例实例。

协程方面又有什么需要注意的吗? 每当多个协程想要访问实例时, 单例结构体就必须返回相同的实例。 正因如此, 单例设计模式的实施工作很容易出错。 下方的例子表示了创建单例的正确方式。

一些值得注意的地方:

  • 最开始时会有 nil检查, 确保 single­Instance单例实例在最开始时为空。 这是为了防止在每次调用 get­Instance方法时都去执行消耗巨大的锁定操作。 如果检查不通过, 则就意味着 single­Instance字段已被填充。
  • single­Instance结构体将在锁定期间创建。
  • 在获取到锁后还会有另一个 nil检查。 这是为了确保即便是有多个协程绕过了第一次检查, 也只能有一个可以创建单例实例。 否则, 所有协程都会创建自己的单例结构体实例。

single.go: 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"sync"
)

var lock = &sync.Mutex{}

type single struct {
}

var singleInstance *single

func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}

return singleInstance
}

main.go: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {

for i := 0; i < 30; i++ {
go getInstance()
}

// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}

output.txt: 执行结果

1
2
3
4
5
6
Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
...

根据: Golang By Example

另一个例子

  1. init函数

我们可以在 init函数中创建单例实例。 这仅适用于实例的早期初始化工作已经确定时。 init函数仅会在包中的每个文件里调用一次, 所以我们可以确定其只会创建一个实例。

  1. sync.Once

sync.Once仅会执行一次操作。 可查看下面的代码:

syncOnce.go: 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"sync"
)

var once sync.Once

type single struct {
}

var singleInstance *single

func getInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}

return singleInstance
}

main.go: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {

for i := 0; i < 30; i++ {
go getInstance()
}

// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}

output.txt: 执行结果

1
2
3
Creating single instance now.
Single instance already created.
Single instance already created.

根据: Golang By Example