Menu
CoddyTech

单例模式

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 91 课,共 104 课。

单例模式确保一个类在整个程序中只有一个实例,并提供全局访问该实例的入口。这适用于需要共享的资源,例如配置管理器、日志记录器或数据库连接池。

核心思路是将构造函数设为私有,这样就没有人能直接创建实例,然后提供一个静态方法来返回唯一的实例:

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // 私有构造函数,防止直接创建实例
    Logger() : logFile("app.log") {}
    
    // 删除拷贝构造函数和赋值运算符
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // 用于访问唯一实例的静态方法
    static Logger& getInstance() {
        static Logger instance;  // 只创建一次,直到程序结束才销毁
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

int main() {
    Logger::getInstance().log("Application started");
    
    Logger& logger = Logger::getInstance();
    logger.log("Processing data");
}

static Logger instance 位于 getInstance() 内部,这是 C++11 的一项特性,称为“魔法静态变量”,它是线程安全的,并且保证只初始化一次。通过删除复制构造函数和赋值运算符,我们可以防止任何人创建单例的副本。

谨慎使用单例模式,因为它会引入全局状态,可能让测试变得更困难。它最适合用于真正需要共享的资源,因为创建多个实例可能会导致问题。

challenge icon

挑战

简单

让我们构建一个 GameSettings 单例,用于管理游戏应用的配置。这是单例模式的理想用例。你需要一个唯一的设置管理器,让游戏的所有部分都能访问它,从而确保整个游戏的配置一致。

你将把代码组织在三个文件中:

  • GameSettings.h:定义用于管理游戏配置的单例类。

    你的 GameSettings 类应将三个设置存储为私有成员:difficulty(字符串)、volume(0-100 的整数)和 fullscreen(布尔值)。将它们初始化为默认值:"Normal"、50 和 false。

    正确实现单例模式:

    • 将构造函数设为私有
    • 删除复制构造函数和赋值运算符
    • 提供一个静态 getInstance() 方法,返回唯一实例的引用

    添加以下公共方法来操作这些设置:

    • setDifficulty(const std::string& diff):更新难度
    • setVolume(int vol):更新音量
    • setFullscreen(bool fs):更新全屏模式
    • displaySettings():输出所有当前设置
  • GameModule.h:创建一个用于访问单例的独立模块。

    定义一个名为 applyGamePreset 的函数,它接受一个预设名称(字符串)。此函数应访问 GameSettings 单例,并根据预设进行配置:

    • "casual":将难度设为 "Easy",音量设为 70,全屏设为 false
    • "competitive":将难度设为 "Hard",音量设为 30,全屏设为 true
    • 其他任何预设:保持默认设置不变

    这展示了程序的不同部分如何访问并修改同一个单例实例。

  • main.cpp:将所有内容整合起来,展示单例的实际用法。

    读取两个输入:

    1. 一个预设名称(字符串)
    2. 一个自定义音量等级(整数)

    首先,在单例上调用 displaySettings(),输出初始默认设置。之后输出一个空行。

    然后,使用你的 applyGamePreset 函数应用预设,并再次输出设置。之后输出一个空行。

    最后,仅使用自定义输入值覆盖音量,并输出最终设置。

displaySettings() 方法应按以下确切格式输出:

Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]

例如,输入 casual 和 85 时:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

输入 competitive 和 20 时:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

注意,main.cpp 和 GameModule.h 都访问同一个单例实例:在一处所做的更改在其他地方也能看到。这就是单例模式用于游戏设置等共享资源的优势。

亲手试试

#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"

using namespace std;

int main() {
    // 读取输入
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: 获取 GameSettings 单例实例
    
    // TODO: 显示初始默认设置
    
    // TODO: 输出一个空行
    
    // TODO: 使用 applyGamePreset 函数应用预设
    
    // TODO: 显示应用预设后的设置
    
    // TODO: 输出一个空行
    
    // TODO: 用 customVolume 覆盖音量设置
    
    // TODO: 显示最终设置
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器