【HF计划模式】05-单例模式

[复制链接]
发表于 2025-11-14 00:33:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
声明:仅为个人学习总结,还请批驳性查察,如有差别观点,接待交换。
择要

《Head First计划模式》第5章条记:团结示例应用和代码,先容单例模式,包罗碰到的题目、接纳的办理方案、以及到达的效果。




1 示例应用

示例应用是巧克力工厂的锅炉控制体系。巧克力锅炉将巧克力和牛奶混淆,加热至沸腾,然后将它们送到制作巧克力棒的下一阶段。
锅炉的状态包罗 boolean empty(是否为空)和 boolean boiled(是否沸腾),相应的状态转换环境如下:
   下面是巧克力锅炉的界说,在实验 fill()、boil()、drain() 利用时,都举行了严格的状态判断,防止出现锅炉空烧、排挤未煮沸巧克力等糟糕环境。
  1. public class ChocolateBoiler {
  2.     private boolean empty;
  3.     private boolean boiled;
  4.     public ChocolateBoiler() {
  5.         empty = true;
  6.         boiled = false;
  7.     }
  8.     public void fill() {
  9.         if (isEmpty()) { // 状态为空时,才可以执行操作
  10.             System.out.println("向锅炉中加满牛奶和巧克力");
  11.             empty = false;
  12.             boiled = false;
  13.         }
  14.     }
  15.     public void drain() {
  16.         if (!isEmpty() && isBoiled()) { // 状态为满并且沸腾时,才可以执行操作
  17.             System.out.println("从锅炉中排出牛奶和巧克力");
  18.             empty = true;
  19.             boiled = false;
  20.         }
  21.     }
  22.     public void boil() {
  23.         if (!isEmpty() && !isBoiled()) { // 状态为满并且未沸腾时,才可以执行操作
  24.             System.out.println("将锅炉中的牛奶和巧克力煮沸");
  25.             boiled = true;
  26.         }
  27.     }
  28.     public boolean isEmpty() { return empty; }
  29.     public boolean isBoiled() { return boiled; }
  30. }
复制代码
除了状态监控监控,为包管体系正常运行,还要制止为一台锅炉创建多个 ChocolateBoiler 实例。否则,由它们共同利用锅炉,环境也会很糟糕。

2 引入计划模式

接下来,我们实验通过计划模式来确保一个类 ChocolateBoiler 只能创建单一实例(单例)。
显然,通过 new ChocolateBoiler() 可以创建一个实例;但是,再次实验 new ChocolateBoiler() 还会创建另一个实例。
以是,我们的目标是让 new ChocolateBoiler() 只能被实验一次。
要告竣目标,可以分为下面 3 个步调。

2.1 私有构造方法

以 MyClass 类为例。
思索题
  1. 下面哪个或哪些选项可以将 MyClass 类实例化?【答案在第 20 行】
  2. public class MyClass {
  3.     // ...
  4.     private MyClass() {}
  5. }
  6. A. MyClass 的包外类
  7. B. MyClass 的同包类
  8. C. MyClass 的子类
  9. D. MyClass 的内部代码
  10. E. 没有任何办法
  11. 答案:D
  12. 解析:一个类的 private 成员(包括构造方法)只能从“它所在类的内部”访问。
复制代码
告竣目标的第1步:界说私有构造方法,制止外部类直接实验 new MyClass() 创建实例。

2.2 静态方法

类的静态成员(静态方法和静态变量),属于类本身,而不属于类的某个实例。
通过在类的内部界说 getInstance() 方法,可以访问类的私有构造方法,创建实例。
  1. public class MyClass {
  2.     // ...
  3.     private MyClass() {}
  4.     public static MyClass getInstance() {
  5.         return new MyClass();
  6.     }
  7. }
复制代码
通过将 getInstance() 声明为 static 静态方法,可以大概在不创建任何实例的环境下,直接利用类名访问 getInstance()。
  1. MyClass obj = MyClass.getInstance();
复制代码
告竣目标的第2步:界说静态方法,由类自身实验 new MyClass() 创建实例,并对外提供获取实例的同一接口。

2.3 静态变量

静态变量属于类本身,用于存储“类级别”的状态或共享数据。由于它与类的任何实例都无关,以是可以用来控制实例的创建。
通过在类的内部界说静态变量 uniqueInstance,控制 MyClass 只能创建单一实例(单例)。
  1. public class MyClass {
  2.     private static MyClass uniqueInstance;  // 在类加载时被初始化为 null
  3.     private MyClass() {}
  4.     public static MyClass getInstance() {
  5.         if (uniqueInstance == null) {       // 在还没有创建任何实例的情况下,可以创建实例,确保实例的唯一性
  6.             uniqueInstance = new MyClass(); // 使用 uniqueInstance 引用该实例
  7.         }
  8.         return uniqueInstance;              // 返回已经创建的实例
  9.     }
  10. }
复制代码
告竣目标的第3步:界说静态变量,引用类的唯一实例,限定 new MyClass() 只能被实验一次。

2.4 经典单例实现

我们利用一个私有构造方法、一个静态方法、一个静态变量,实现了经典的单例模式。
  1. public class Singleton {
  2.     // 一个静态变量(私有),引用 Singleton 的唯一实例
  3.     private static Singleton uniqueInstance;
  4.     // 在这里添加其它有用的变量
  5.     // 将构造方法声明为私有的,只有 Singleton 可以实例化这个类
  6.     private Singleton() {
  7.         // ...
  8.     }
  9.     // 一个静态方法(公共),用于创建唯一的 Singleton 实例,并将其返回
  10.     public static Singleton getInstance() {
  11.         if (uniqueInstance == null) {
  12.             uniqueInstance = new Singleton();
  13.         }
  14.         return uniqueInstance;
  15.     }
  16.     // 在这里添加其它有用的方法
  17.     // 当然,Singleton 是一个正常的类,会有一些实现相应功能的变量和方法
  18. }
复制代码
在单例模式中:


  • 由一个类来管理本身的唯一实例,要想访问实例,只能通过该类;
  • 在必要访问实例时,只必要调用该类提供的静态方法(即,该实例的全局访问点)。

2.5 单例模式界说

   单例模式(Singleton Pattern)
包管一个类仅有一个实例,并提供一个访问它的全局访问点。
Ensure a class only has one instance, and provide a global point of access to it.
   

  • 私有(或掩护)构造函数 Singleton(),确保外部无法直接实例化;
  • 私有(或掩护)静态成员变量 uniqueInstance,引用 Singleton 的唯一实例;
  • 公共静态成员方法 getInstance(),提供访问 Singleton 唯一实例的全局接口;
  • 业务干系的数据 usefulSingletonData 和方法 usefulSingletonMethod(),用于实现类的详细功能。
单例模式的优点:

  • 控制实例访问
    通过 getInstance() 方法,可以严格控制实例的访问机遇和访问方式。
  • 相比全局变量的上风

    • 实例唯一性:单例可以包管实例唯一,而全局变量无法包管;
    • 延伸初始化:单例可以根据必要创建实例,而全局变量在步伐启动时就会创建(无论是否会用到);
    • 定名空间占用:单例的实例被封装在类内部,而全局变量直接占用全局定名空间(定名空间污染)。

  • 实例数目可变
    通过修改 2.3 静态变量 的实现,也可以让 Singleton 类管理本身的多个实例。
延伸阅读:《计划模式:可复用面向对象软件的底子》 3.5 Singleton(单件)— 对象创建型模式 [P96-102]

2.6 第1版改进

接纳单例模式,已经实现让一个类只能创建单一实例。下面是改进后的 ChocolateBoiler 类:
  1. public class ChocolateBoiler {
  2.     private boolean empty;
  3.     private boolean boiled;
  4.    
  5.     // 增加静态变量、静态方法,修改构造方法为私有
  6.     private static ChocolateBoiler uniqueInstance;
  7.     private ChocolateBoiler() {
  8.         empty = true;
  9.         boiled = false;
  10.     }
  11.     public static ChocolateBoiler getInstance() {
  12.         if (uniqueInstance == null) {
  13.             uniqueInstance = new ChocolateBoiler();
  14.         }
  15.         return uniqueInstance;
  16.     }
  17.     // 后面的代码没有变化
  18.     public void fill() {
  19.         if (isEmpty()) { // 状态为空时,才可以执行操作
  20.             System.out.println("向锅炉中加满牛奶和巧克力");
  21.             empty = false;
  22.             boiled = false;
  23.         }
  24.     }
  25.     public void drain() {
  26.         if (!isEmpty() && isBoiled()) { // 状态为满并且沸腾时,才可以执行操作
  27.             System.out.println("从锅炉中排出牛奶和巧克力");
  28.             empty = true;
  29.             boiled = false;
  30.         }
  31.     }
  32.     public void boil() {
  33.         if (!isEmpty() && !isBoiled()) { // 状态为满并且未沸腾时,才可以执行操作
  34.             System.out.println("将锅炉中的牛奶和巧克力煮沸");
  35.             boiled = true;
  36.         }
  37.     }
  38.     public boolean isEmpty() { return empty; }
  39.     public boolean isBoiled() { return boiled; }
  40. }
复制代码

3 碰到题目

糟糕!在利用多线程对巧克力锅炉控制器举行优化后,锅炉发生了溢出!

我们来查找一下题目的缘故原由。
下面是锅炉控制器 BoilerController 中的干系代码:
  1. ChocolateBoiler boiler = ChocolateBoiler.getInstance();
  2. boiler.fill();   // 加满
  3. boiler.boil();   // 煮沸
  4. boiler.drain();  // 排出
复制代码
如今有两个线程都必要实验上述代码:


  • 假如它们引用雷同的 ChocolateBoiler 实例,就会共享划一的状态,并通过对状态的严格监控监控,让锅炉运转精良;(此处忽略数据竞争)
  • 假如它们引用差别的 ChocolateBoiler 实例,就会拥有各自的状态,假如一个实例已经加满锅炉,而另一个实例照旧空置状态,并继续参加质料,就会导致锅炉溢出。
但是,ChocolateBoiler 已经界说为单例,这两个线程还会引用差别的实例吗?
我们来细致分析一下 getInstance() 的界说,以及它大概的实验时序。
  1. public static ChocolateBoiler getInstance() {
  2.     if (uniqueInstance == null) {
  3.         uniqueInstance = new ChocolateBoiler();
  4.     }
  5.     return uniqueInstance;
  6. }
复制代码
时序线程1线程2uniqueInstance值1ChocolateBoiler.getInstance()null2if (uniqueInstance == null)null3挂起ChocolateBoiler.getInstance()null4if (uniqueInstance == null)null5uniqueInstance = new ChocolateBoiler();object16return uniqueInstance;object17uniqueInstance = new ChocolateBoiler();object28return uniqueInstance;object2在多线程环境下,果然有大概创建两个 ChocolateBoiler 实例 object1 和 object2。
为了确保实例的创建是线程安全的,我们有多种可选方案,包罗同步方法、急迫实例化、双重查抄锁定等。

3.1 多线程方案1:同步方法

只要把 getInstance() 酿成同步方法(添加 synchronized 关键字),就可以包管实例创建的线程安全性。
   Java 概念:内涵锁(intrinsic lock)
每个 Java 对象都有一个内涵锁,得到对象的内涵锁就可以大概独占该对象的访问权,试图访问被锁定对象的线程将被壅闭,直到持有该锁的线程开释锁。利用 synchronized 关键字可以得到对象的内涵锁。
    Java 概念:方法同步(Method Synchronization)
  

  • 当一个线程调用一个对象的非静态 synchronized 方法时,它会在方法实验之前,自动实验得到该对象的内涵锁;在方法返回之前,线程不停持有锁。
  • 一旦某个线程锁定了某个对象,其他线程就不能实验同一个对象的“同一方法或其他同步方法”,只能壅闭等候,直到这个锁再次酿成可用的为止。
  • 锁还可以重入(reentrant),这意味着持有锁的线程可以调用同一对象上的其他同步方法;当最外层同步方法返回时,会开释该对象的内涵锁。
  • 静态方法也可以同步,在这种环境下,会利用与该方法的类关联的 Class 对象的锁(每个类都有一个对应的 Class 对象,如 Singleton.class,包罗该类的元数据)。
  1. public class Singleton {
  2.     private static Singleton uniqueInstance;
  3.     private Singleton() {}
  4.     // synchronized 保证没有两个线程可以同时执行 getInstance()
  5.     // 一旦某个线程开始执行 getInstance(),就会获得锁;其它线程再调用 getInstance(),会阻塞等待,直到持有锁的线程释放锁
  6.     public static synchronized Singleton getInstance() {
  7.         if (uniqueInstance == null) {
  8.             uniqueInstance = new Singleton();
  9.         }
  10.         return uniqueInstance;
  11.     }
  12.     public String getDescription() { return "I'm a thread safe Singleton!"; }
  13. }
复制代码
同步方法实现简朴,但是也有显着的缺点:

  • 运行时开销大:同步一个方法大概会使性能降落 100 倍(synchronizing a method can decrease performance by a factor of 100);
  • 存在不须要的资源浪费:实际上,只有第一次实验 getInstance() 创建实例时,才必要同步;然而,如今每次实验 getInstance() 都必要同步。
不外,假如 getInstance() 的性能对应用来说并不紧张,那么利用同步方法也没有题目。

3.2 多线程方案2:急迫实例化

在调用 getInstance() 时创建实例,被称为延伸初始化(Lazy Initialization);
与之相对的,可以在类加载时直接创建实例,即急迫初始化(Eager Initialization);由于类加载具有线程安全性,以是实例的创建也是线程安全的。
  1. public class Singleton {
  2.     // 在类加载时直接创建实例,初始化静态变量
  3.     private static Singleton uniqueInstance = new Singleton();
  4.     private Singleton() {}
  5.     // 直接返回已经创建的实例
  6.     public static Singleton getInstance() {
  7.         return uniqueInstance;
  8.     }
  9.     public String getDescription() { return "I'm a thread safe Singleton!"; }
  10. }
复制代码
急迫实例化的特点和干系影响如下:


  • 特点:肯定会创建实例;
    影响:假如应用有大概不利用实例,那么会造成不须要的资源浪费。
  • 特点:在应用启动时就会创建实例;
    影响:假如实例的创建或运行比力斲丧资源,那么会给应用带来肯定的负担。
假如应用必要尽早的利用实例,大概即便利用的时间比力晚,但实例的创建和运行负担并不重,那么也可以选择急迫实例化。

3.3 多线程方案3:双重查抄锁定

双重查抄锁定(Double-Checked Locking):先查抄实例是否已经创建,假如尚未创建,才举行同步。
以此镌汰 同步方法 中 getInstance() 对同步的利用。(必要 Java 5 及以后版本)
   Java 概念:块同步(Block Synchronization)
Java 答应利用 synchronized 关键字来锁定任何对象,从而实今世码块的同步。
synchronized(object) { // 在 object 被锁定的环境下实验某些利用 }
在块中的代码实验之后,锁会被开释。
    Java 概念:可见性(Visibility)
在一个单线程步伐中,读取变量的值总是会得到末了写入该变量的值。但是,在 Java 的多线程应用步伐中,一个线程大概看不到另一个线程所做的更改,除非在数据上实验的利用是同步的。然而,同步是有代价的。假如想要的是可见性,而不必要互斥,那么可以利用 volatile 关键字,该关键字可以确保当一个线程修改了变量的值之后,新值对于其他线程立刻可见。
    Java 概念:指令重排序(Instruction Reordering)
编译器或处理处罚器为了优化步伐性能,大概会改变指令的实验序次。
比方对于 uniqueInstance = new Singleton(); 语句,其包罗的步调表现如下:
  

  • memory = allocate(sizeof(Singleton.class)); 在堆内存中为对象分配空间
  • construct(memory, Singleton.class); 在分配的内存空间上调用构造函数来初始化对象
  • uniqueInstance = memory; 将对象引用指向分配的内存空间
  在举行指令重排序后,步调3大概会在步调2之前实验。假如另一个线程在“步调3之后、步调2之前”访问 uniqueInstance,就会获取到一个未完全初始化的实例。通过将 uniqueInstance 声明为 volatile,可以克制这种重排序。如许,当 uniqueInstance 不为 null 时,它所引用的实例就是完全初始化的。
  1. public class Singleton {
  2.     // 将 uniqueInstance 声明为 volatile,确保其可见性,并禁止指令重排序
  3.     private volatile static Singleton uniqueInstance;
  4.     private Singleton() {}
  5.     public static Singleton getInstance() {
  6.         if (uniqueInstance == null) {                 // 【第一次检查】实例是否创建,只有尚未创建,才进入同步块;
  7.             synchronized (Singleton.class) {          // 尝试加锁,如果其它线程已经加锁,则阻塞等待;
  8.                 if (uniqueInstance == null) {         // 成功加锁后,【再次检查】实例是否尚未创建,
  9.                     uniqueInstance = new Singleton(); // 因为在阻塞等待的过程中,其它线程可能已经创建实例。
  10.                 }
  11.             }
  12.         }
  13.         return uniqueInstance;
  14.     }
  15.     public String getDescription() { return "I'm a thread safe Singleton!"; }
  16. }
复制代码
假如利用同步方法存在性能题目,那么利用双重查抄锁定,则可以分身性能和线程安全。

3.4 别的注意事项

除了创建实例时的线程安全题目,在 java 中,利用单例尚有一些别的注意事项:

  • 类加载器题目

    • 题目形貌:假如有两个或多个类加载器,就可以多次加载同一个类(每个类加载器一次)。假如这个类刚好是一个单例,就会有多于一个的实例。
    • 办理办法:确保单例通过同一个类加载器加载,通常利用体系类加载器(即启动类加载器)加载单例。

  • 反射题目

    • 题目形貌:通过反射可以调用类的私有构造方法,因此大概会创建类的多个实例。
    • 办理办法:在构造方法中添加防御性代码,防止通过反射创建多个实例。比方,可以在构造方法中查抄是否已经存在实例,假如存在则抛出非常。

  • 序列化和反序列化题目

    • 题目形貌:当单例实现了 Serializable 接口时,序列化会将对象的状态生存下来,之后反序列化可以重修对象,如许大概会创建类的多个实例。
    • 办理办法:在单例中添加一个 readResolve 方法,该方法在反序列化时会被调用,可以返回单例实例,而不是创建一个新的实例。


3.5 利用摆列

   Java 概念:摆列(Enum)
在 Java 中,摆列是一种特别的类,是 java.lang.Enum 的子类,它的特性包罗:
  

  • 摆列类默认具有私有的构造方法,而且不答应显式界说非私有构造方法,因此无法从外部实例化;而且也不能通过反射来访问构造方法;
  • 摆列实例在类被加载到 JVM 时静态初始化,包管了实例的唯一性和线程安全性;
  • 摆列类在序列化和反序列化的过程中,会由 JVM 包管摆列实例的唯一性;
  • 每个摆列常量自动被视为 public static final,而且是摆列范例的一个实例;
  • 摆列类也可以界说本身的方法和变量。
  由于 Java 会包管摆列类中每个摆列常量的唯一性,以是通过界说一个包罗单个摆列常量的摆列类,就可以自然地实现单例模式。
  1. public enum Singleton {
  2.     UNIQUE_INSTANCE;
  3.     // 可以添加有用的变量和方法
  4.     public String getDescription() {
  5.         return "I'm a thread safe Singleton!";
  6.     }
  7. }
复制代码
摆列的利用:
  1. Singleton singleton = Singleton.UNIQUE_INSTANCE;
  2. System.out.println(singleton.getDescription());
复制代码
利用摆列实现单例,代码简便明白,而且可以制止前文提到的全部单例题目,包罗创建实例时的线程安全、类加载题目、反射题目、以及序列化和反序列化题目。因此,摆列是实现单例模式的一种保举方式。

4 示例代码


4.1 Java 示例

双重查抄锁定方式:
  1. // ChocolateBoiler.java
  2. public class ChocolateBoiler {
  3.     private boolean empty;
  4.     private boolean boiled;
  5.     private volatile static ChocolateBoiler uniqueInstance;
  6.     private ChocolateBoiler() {
  7.         empty = true;
  8.         boiled = false;
  9.         System.out.println("[" + Thread.currentThread().getName() + "] 创建巧克力锅炉实例,初始状态:空、未沸");
  10.     }
  11.     public static ChocolateBoiler getInstance() {
  12.         if (uniqueInstance == null) {
  13.             synchronized (ChocolateBoiler.class) {
  14.                 if (uniqueInstance == null) {
  15.                     uniqueInstance = new ChocolateBoiler();
  16.                 }
  17.             }
  18.         }
  19.         System.out.println("[" + Thread.currentThread().getName() + "] 返回巧克力锅炉实例,当前状态:" + (uniqueInstance.isEmpty() ? "空" : "满") + "、" + (uniqueInstance.isBoiled() ? "沸腾" : "未沸"));
  20.         return uniqueInstance;
  21.     }
  22.     public synchronized void fill() {
  23.         System.out.println("[" + Thread.currentThread().getName() + "] 尝试加满,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
  24.         if (isEmpty()) {
  25.             System.out.println(" => 向锅炉中加满牛奶和巧克力");
  26.             empty = false;
  27.             boiled = false;
  28.         }
  29.     }
  30.     public synchronized void drain() {
  31.         System.out.println("[" + Thread.currentThread().getName() + "] 尝试排出,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
  32.         if (!isEmpty() && isBoiled()) {
  33.             System.out.println(" => 从锅炉中排出牛奶和巧克力");
  34.             empty = true;
  35.             boiled = false;
  36.         }
  37.     }
  38.     public synchronized void boil() {
  39.         System.out.println("[" + Thread.currentThread().getName() + "] 尝试煮沸,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
  40.         if (!isEmpty() && !isBoiled()) {
  41.             System.out.println(" => 将锅炉中的牛奶和巧克力煮沸");
  42.             boiled = true;
  43.         }
  44.     }
  45.     public synchronized boolean isEmpty() { return empty; }
  46.     public synchronized boolean isBoiled() { return boiled; }
  47. }
复制代码
摆列方式:
  1. // ChocolateBoilerEnum.java
  2. public enum ChocolateBoilerEnum {
  3.     UNIQUE_INSTANCE;
  4.     private boolean empty;
  5.     private boolean boiled;
  6.     private ChocolateBoilerEnum() {
  7.         empty = true;
  8.         boiled = false;
  9.         System.out.println("[" + Thread.currentThread().getName() + "] 创建巧克力锅炉实例,初始状态:空、未沸");
  10.     }
  11.     public synchronized void fill() { /* 代码相同 */ }
  12.     public synchronized void drain() { /* 代码相同 */ }
  13.     public synchronized void boil() { /* 代码相同 */ }
  14.     public synchronized boolean isEmpty() { /* 代码相同 */ }
  15.     public synchronized boolean isBoiled() { /* 代码相同 */ }
  16. }
复制代码
测试代码:
  1. // BoilerController.java
  2. public class BoilerController {
  3.     public static void main(String args[]) {
  4.         Runnable boilerTask = new Runnable() {
  5.             @Override
  6.             public void run() {
  7.                 ChocolateBoiler boiler = ChocolateBoiler.getInstance();
  8.                 // ChocolateBoilerEnum boiler = ChocolateBoilerEnum.UNIQUE_INSTANCE;
  9.                 boiler.fill();   // 加满
  10.                 boiler.boil();   // 煮沸
  11.                 boiler.drain();  // 排出
  12.             }
  13.         };
  14.         Thread thread1 = new Thread(boilerTask);
  15.         Thread thread2 = new Thread(boilerTask);
  16.         thread1.start();
  17.         thread2.start();
  18.     }
  19. }
复制代码

4.2 C++11 示例


4.2.1 Meyers Singleton
Meyers Singleton 是一种在 C++ 中实现单例模式的简便方法,由 Scott Meyers(Effective C++ 的作者)提出。
  1. struct Singleton {
  2.     static Singleton& getInstance() {
  3.         static Singleton uniqueInstance;
  4.         return uniqueInstance;
  5.     }
  6.     Singleton(const Singleton&) = delete;
  7.     Singleton& operator=(const Singleton&) = delete;
  8. private:
  9.     Singleton() = default;
  10. };
复制代码
  这种方法基于 C++11 的包管,即局部静态对象会在“函数第一次实验到该对象的界说处”时被初始化(同时具有线程安全性)。作为额外的利益,假如从未调用“模拟非局部静态对象”的函数(即getInstance()函数,利用在函数内部界说的“局部静态对象”,模拟在全局/定名空间范围内界说的“非局部静态对象”),就永世不会产生构造和析构该对象的开销。
Scott Myers says: “This approach is founded on C++'s guarantee that local static objects are initialized when the object’s definition is first encountered during a call to that function.” … “As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object.”
  
4.2.2 ChocolateBoiler
局部静态变量方式(Meyers Singleton):
  1. struct ChocolateBoiler {
  2.   static ChocolateBoiler& getInstance() {
  3.     // 对于局部静态变量,由 C++11 保证只在第一次执行到变量定义处时,进行初始化,并且是线程安全的
  4.     static ChocolateBoiler uniqueInstance;
  5.     std::cout << "[" << std::this_thread::get_id() << "] 返回巧克力锅炉实例,当前状态:" << (uniqueInstance.isEmpty() ? "空" : "满") << "、" << (uniqueInstance.isBoiled() ? "沸腾" : "未沸") << '\n';
  6.     return uniqueInstance;
  7.   }
  8.   void fill() {
  9.     std::lock_guard<std::recursive_mutex> guard(mtx);
  10.     std::cout << "[" << std::this_thread::get_id() << "] 尝试加满,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
  11.     if (isEmpty()) {
  12.       std::cout << " => 向锅炉中加满牛奶和巧克力\n";
  13.       empty = false;
  14.       boiled = false;
  15.     }
  16.   }
  17.   void drain() {
  18.     std::lock_guard<std::recursive_mutex> guard(mtx);
  19.     std::cout << "[" << std::this_thread::get_id() << "] 尝试排出,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
  20.     if (!isEmpty() && isBoiled()) {
  21.       std::cout << " => 从锅炉中排出牛奶和巧克力\n";
  22.       empty = true;
  23.       boiled = false;
  24.     }
  25.   }
  26.   void boil() {
  27.     std::lock_guard<std::recursive_mutex> guard(mtx);
  28.     std::cout << "[" << std::this_thread::get_id() << "] 尝试煮沸,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
  29.     if (!isEmpty() && !isBoiled()) {
  30.       std::cout << " => 将锅炉中的牛奶和巧克力煮沸\n";
  31.       boiled = true;
  32.     }
  33.   }
  34.   bool isEmpty() const {
  35.     std::lock_guard<std::recursive_mutex> guard(mtx);
  36.     return empty;
  37.   }
  38.   bool isBoiled() const {
  39.     std::lock_guard<std::recursive_mutex> guard(mtx);
  40.     return boiled;
  41.   }
  42.   ChocolateBoiler(const ChocolateBoiler&) = delete;
  43.   ChocolateBoiler& operator=(const ChocolateBoiler&) = delete;
  44. private:
  45.   ChocolateBoiler() : empty(true), boiled(false) {
  46.     std::cout << "[" << std::this_thread::get_id() << "] 创建巧克力锅炉实例,初始状态:空、未沸\n";
  47.   }
  48.   bool empty;
  49.   bool boiled;
  50.   static std::recursive_mutex mtx;
  51. };
  52. std::recursive_mutex ChocolateBoiler::mtx;
复制代码
测试代码:
  1. #include <iostream>
  2. #include <mutex>
  3. #include <thread>
  4. // 在这里添加相关接口和类的定义
  5. int main() {
  6.   std::function<void()> boilerTask = []() {
  7.     ChocolateBoiler& boiler = ChocolateBoiler::getInstance();
  8.     boiler.fill();
  9.     boiler.boil();
  10.     boiler.drain();
  11.   };
  12.   std::thread t1(boilerTask);
  13.   std::thread t2(boilerTask);
  14.   t1.join();
  15.   t2.join();
  16. }
复制代码

4.2.3 一次性互斥
与 Java 的双重查抄锁定相比,C++11 提供了更为简便的“一次性互斥”机制。
通过 std:nce_flag 类和 std::call_once() 函数模板来实现一次性互斥,确保纵然有多个线程、多次、同时调用某个函数(可调用对象),其只会被实验一次。复习回首 线程池2-线程互斥 => 3.1.3 一次性互斥。
  1. struct Singleton {
  2.   static Singleton& getInstance() {
  3.     std::call_once(initFlag, []() { uniqueInstance.reset(new Singleton()); });
  4.     return *uniqueInstance;
  5.   }
  6.   Singleton(const Singleton&) = delete;
  7.   Singleton& operator=(const Singleton&) = delete;
  8. private:
  9.   Singleton() = default;
  10.   static std::unique_ptr<Singleton> uniqueInstance;
  11.   static std::once_flag initFlag;
  12. };
  13. std::unique_ptr<Singleton> Singleton::uniqueInstance;
  14. std::once_flag Singleton::initFlag;
复制代码

5 计划工具箱


5.1 OO 底子

OO 底子回首

  • 抽象(Abstraction)
  • 封装(Encapsulation)
  • 继续(Inheritance)
  • 多态(Polymorphism)

5.2 OO 原则

5.2.1 新原则
本章没有先容新的 OO 原则。
5.2.2 原则回首

  • 封装变革。
    Encapsulate what varies.
  • 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  • 优先利用组合,而不是继续。
    Favor composition over inheritance.
  • 只管做到交互对象之间的松耦合计划。
    Strive for loosely coupled designs between objects that interact.
  • 类应该对扩睁开放,对修改关闭。
    Classes should be open for extension, but closed for modification.
  • 依赖抽象,不依赖详细类。
    Depend on abstractions. Do not depend on concrete classes.

5.3 OO 模式

5.3.1 新模式
单例模式(Singleton Pattern)


  • 确保一个类只有一个实例,并提供一个全局访问点。
    The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it.
5.3.2 模式回首
1 创建型模式(Creational Patterns)
   创建型模式与对象的创建有关。
Creational patterns concern the process of object creation.
  

  • 工厂方法(Factory Method)

    • 界说了一个创建对象的接口,但由子类决定要实例化哪个类。
      The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate.
    • 工厂方法让类把实例化推迟到子类。
      Factory Method lets a class defer instantiation to subclasses.

  • 抽象工厂(Abstract Factory)

    • 提供一个接口,创建干系或依赖对象的眷属,而不必要指定详细类。
      The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

2 结构型模式(Structural Patterns)
   结构型模式处理处罚类或对象的组合。
Structural patterns deal with the composition of classes or objects.
  

  • 装饰者模式(Decorator Pattern)

    • 动态地给一个对象添加一些额外的职责。
      The Decorator Pattern attaches additional responsibilities to an object dynamically.
    • 就增长功能来说,装饰者模式相比天生子类更为机动。
      Decorators provide a flexible alternative to subclassing for extending functionality.

3 举动型模式(Behavioral Patterns)
   举动型模式形貌类或对象之间的交互方式以及职责分配方式。
Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.
  

  • 计谋模式(Strategy Pattern)

    • 界说一个算法眷属,把此中的算法分别封装起来,使得它们之间可以相互更换。
      Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.
    • 让算法的变革独立于利用算法的客户。
      Strategy lets the algorithm vary independently from clients that use it.

  • 观察者模式(Observer Pattern)

    • 界说对象之间的一对多依赖,
      The Observer Pattern defines a one-to-many dependency between objects
    • 如许一来,当一个对象改变状态时,它的全部依赖者都会被关照并自动更新。
      so that when one object changes state, all of its dependents are notified and updated automatically.

参考


  • [美]弗里曼、罗布森著,UMLChina译.Head First计划模式.中国电力出书社.2022.2
  • [美]伽玛等著,李英军等译.计划模式:可复用面向对象软件的底子.呆板工业出书社.2019.3
  • wickedlysmart: Head First计划模式 Java 源码
  • [加]布迪·克尼亚万著,沈泽刚译.Java经典入门指南.人民邮电出书社.2020.6

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表