Menu
CoddyTech

单例模式

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

单例模式是一种创建型设计模式,可确保一个类在应用程序的整个生命周期中只有一个实例。它还提供了一个全局访问点,用于访问该实例。

当你需要一个对象来协调整个系统中的操作时,这种模式很有用,例如数据库连接、配置管理器或日志记录器。创建多个这样的实例会浪费资源,或导致行为不一致。

下面介绍如何在 PHP 中实现单例:

<?php
class Database {
    private static ?Database $instance = null;
    
    private function __construct() {
        // 私有构造函数可防止直接创建实例
    }
    
    public static function getInstance(): Database {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function query(string $sql): string {
        return "Executing: $sql";
    }
}

$db1 = Database::getInstance();
$db2 = Database::getInstance();

echo $db1 === $db2 ? "Same instance" : "Different instances";

输出:

Same instance

这种模式依赖三个关键元素:一个私有构造函数,用于阻止直接使用 new;一个静态属性,用于保存唯一实例;以及一个静态方法,用于在首次调用时创建实例,并在后续调用时返回该实例。

为了让 Singleton 真正万无一失,你还应该阻止克隆和反序列化:

<?php
private function __clone() {}
public function __wakeup() {
    throw new Exception("Cannot unserialize singleton");
}
challenge icon

挑战

简单

让我们使用单例模式构建一个配置管理器。在实际应用中,你通常需要一个统一的配置设置来源:存在多个配置对象可能会导致系统各处的行为不一致。

你将把代码分成两个文件:

  • Config.php:创建一个实现单例模式的 Config 类。你的配置管理器应当:
    • 将设置存储在一个私有数组属性中
    • 使用私有构造函数,并将设置数组初始化为空
    • 提供一个静态 getInstance() 方法,用于返回唯一实例
    • 将 __clone() 设为私有,防止克隆
    • 包含一个 set(string $key, string $value): void 方法,用于存储设置
    • 包含一个 get(string $key): ?string 方法,用于返回指定键对应的值;如果键不存在,则返回 null
    • 包含一个 getAll(): array 方法,用于返回所有设置
  • main.php:引入 Config 文件。你会收到两个输入:一个键和一个值。

    获取 Config 实例,并设置一个默认项,键为 "app_name",值为 "MyApp"。

    再次调用 getInstance(),获取 Config 实例的另一个引用,并使用这个引用,根据输入的键和值设置另一项。

    最后,验证单例是否正常工作:

    • 根据两个引用是否指向同一个对象,打印 "Same instance: true" 或 "Same instance: false"(使用 === 进行比较)
    • 打印 "Settings count: [count]",显示存储的设置总数(对 getAll() 的结果使用 count())
    • 打印 "App name: [value]",显示通过第二个引用获取的 app_name 设置

这个挑战展示了单例模式的核心优势:无论你调用 getInstance() 多少次,得到的始终是同一个对象,从而确保应用的所有部分共享相同的配置状态。

亲手试试

<?php

require_once 'Config.php';

// 读取输入
$key = trim(fgets(STDIN));
$value = trim(fgets(STDIN));

// TODO: 获取 Config 实例,并将 "app_name" 设置为 "MyApp"

// TODO: 再次调用 getInstance(),获取 Config 的第二个引用

// TODO: 使用第二个引用,根据输入的 $key 和 $value 设置配置项

// TODO: 验证 Singleton 是否正常工作:
// 根据 === 比较结果,输出 "Same instance: true" 或 "Same instance: false"
// 输出 "Settings count: [count]",显示配置项总数
// 输出 "App name: [value]",显示通过第二个引用获取的 app_name

?>
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: PHP 在线编译器