参考:

操作系统 - 线程安全 - 学习_操作系统课设线程安全型-CSDN博客

PHP - 版本选择 - 线程安全与非线程安全 - 学习/实践_php线程安全和非线程安全-CSDN博客

PHP - 线程安全 - 疑问与答案-CSDN博客

PHP - 静态方法与非静态方法的区别[含性能,内存消耗] - 学习/实践_非静态方法节省内存吗-CSDN博客

设计模式 - 单例模式与工厂模式_php单例模式和工厂模式-CSDN博客

<?php

namespace App\Services\Traits;

trait SingletonTrait
{
    private static $instance = [];

    public static function getInstance(...$arg)
    {
        if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self)) {
            self::$instance[static::class] = new static(...$arg);
        }
        return self::$instance[static::class];
    }
}

分析上面的一段代码(同事的编码)

分析如下:

单例模式实现代码解析

这段代码实现了一个多态单例模式(Multiton pattern),即允许每个子类拥有自己的单例实例。下面我将详细解释这段代码的各个部分:

代码功能概述

public static function getInstance(...$arg)
{
    if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self)) {
        self::$instance[static::class] = new static(...$arg);
    }
    return self::$instance[static::class];
}

这是一个静态方法,用于获取当前类的单例实例,具有以下特点:

  • 支持多态(子类有自己的单例实例)
  • 支持可变参数构造
  • 线程不安全(PHP本身是单线程的,所以不是问题)

逐行解析

1. 方法签名

public static function getInstance(...$arg)
  • static:静态方法,通过类名直接调用
  • ...$arg:可变参数,允许传入任意数量的参数给构造函数

2. 条件判断

if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self))

两个条件满足其一就会创建新实例:

  1. !isset(self::$instance[static::class]):当前类的实例不存在
  2. !(self::$instance[static::class] instanceof self):存储的实例不是当前类或其子类的实例

3. 创建新实例

self::$instance[static::class] = new static(...$arg);
  • new static(...$arg):后期静态绑定,创建当前调用类的实例
  • ...$arg:将传入的参数展开传递给构造函数

4. 返回实例

return self::$instance[static::class];

返回当前类的单例实例

关键概念解释

1. static::class vs self::class

  • static::class:返回实际调用的类名(后期静态绑定)
  • self::class:返回定义该代码的类名

示例:

class ParentClass {
    public static function who() {
        echo static::class;
    }
}

class ChildClass extends ParentClass {}

ChildClass::who(); // 输出 "ChildClass"

2. 多态单例模式

这个实现允许:

class A {
    use SingletonTrait;
}

class B extends A {}

$a1 = A::getInstance(); // A的实例
$a2 = A::getInstance(); // 同上实例
$b1 = B::getInstance(); // B的实例(不同于A的实例)

3. 可变参数 ...$arg

允许灵活地传递构造函数参数:

class Config {
    public function __construct($path, $default = []) {}
}

$config = Config::getInstance('/path/to/config', ['key' => 'value']);

完整实现示例

通常这种单例模式会配合静态属性使用:

trait SingletonTrait {
    private static $instances = [];

    public static function getInstance(...$args) {
        if (!isset(self::$instances[static::class]) || 
            !(self::$instances[static::class] instanceof static)) {
            self::$instances[static::class] = new static(...$args);
        }
        return self::$instances[static::class];
    }

    // 防止外部实例化
    private function __construct() {}
    private function __clone() {}
    private function __wakeup() {}
}

class Database {
    use SingletonTrait;
    
    private $connection;
    
    protected function __construct($host, $user, $pass) {
        $this->connection = new PDO("mysql:host=$host", $user, $pass);
    }
}

$db = Database::getInstance('localhost', 'root', 'password');

使用场景

  • 配置管理:全局唯一的配置实例
  • 数据库连接:避免重复创建连接
  • 日志系统:统一的日志记录器
  • 缓存系统:全局缓存管理

注意事项

  • 不是线程安全:PHP本身是单线程的,但在PHP-FPM等多进程环境下仍需注意
  • 测试困难:单例在单元测试中难以mock
  • 生命周期:在长时间运行的进程(如Swoole)中可能导致内存泄漏
  • 继承问题:子类化单例需要谨慎设计

替代方案

对于现代PHP开发,可以考虑:

  • 依赖注入容器:
$container = new Container();
$container->singleton(Database::class, function() {
    return new Database();
});
  • 静态工厂:
class Factory {
    private static $dbs = [];

    public static function getDb($name) {
        if (!isset(self::$dbs[$name])) {
            self::$dbs[$name] = new Database();
        }
        return self::$dbs[$name];
    }
}

这段代码展示了PHP中灵活运用静态方法和后期静态绑定实现多态单例模式的经典方式,是很多框架和库中常见的设计模式实现。

下面代码的区别

if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self)) 
//与 
if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof static)) 
//与
if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof self)) 
//的区别

PHP 中静态属性访问与实例检查的区别

这几种写法在 PHP 中有重要区别,主要涉及静态绑定和实例检查的行为差异。下面我将详细分析每种写法的含义和适用场景。

四种写法的对比分析

1. 原始写法

if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self))
  • self::$instance:访问定义该代码的类中的静态属性 $instance
  • instanceof self:检查是否为定义该代码的类的实例
  • 特点:静态属性访问和实例检查都使用早期静态绑定

2. 变体1

if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof static))
  • static::$instance:后期静态绑定访问静态属性
  • instanceof static:检查是否为实际调用类的实例
  • 特点:完全的后期静态绑定,最符合多态单例的需求

3. 变体2

if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof self))
  • static::$instance:后期静态绑定访问静态属性
  • instanceof self:检查是否为定义该代码的类的实例
  • 特点:混合模式,属性访问是多态的,但实例检查是静态的

详细区别

静态属性访问的区别

写法

行为

继承时的表现

self::$instance

总是访问定义该属性的类

子类无法覆盖静态属性

static::$instance

动态解析访问当前类

子类可以有自己的静态属性

实例检查的区别

写法

行为

继承时的表现

instanceof self

检查是否为定义类的实例

严格检查,不接受子类实例

instanceof static

检查是否为当前类的实例

多态检查,接受子类实例

实际效果演示

class ParentClass {
    protected static $instance = [];
    
    public static function getInstance() {
        // 测试四种不同写法
        var_dump([
            'self::$instance' => isset(self::$instance[static::class]),
            'static::$instance' => isset(static::$instance[static::class]),
            'instanceof self' => self::$instance[static::class] instanceof self,
            'instanceof static' => self::$instance[static::class] instanceof static
        ]);
    }
}

class ChildClass extends ParentClass {
    protected static $instance = [];
}

ChildClass::getInstance();

最佳实践建议

  • 多态单例模式:使用完全后期静态绑定的版本
if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof static))
  • 严格单例模式:如果希望所有子类共享同一个实例
if (!isset(self::$instance[static::class]) || !(self::$instance[static::class] instanceof self))
  • 避免混合模式:static::$instance + instanceof self 通常不是好的设计

性能考虑

  • static::self:: 有轻微性能开销(动态解析)
  • 在单例模式中这种差异可以忽略不计
  • 高频调用场景才需要考虑优化

完整示例

trait SingletonTrait {
    protected static $instances = [];

    public static function getInstance(...$args) {
        // 最佳实践:完全后期静态绑定
        if (!isset(static::$instances[static::class]) || 
            !(static::$instances[static::class] instanceof static)) {
            static::$instances[static::class] = new static(...$args);
        }
        return static::$instances[static::class];
    }
}

class Logger {
    use SingletonTrait;
    
    protected static $instances = []; // 每个类有自己的静态属性
    
    private function __construct() {}
}

class FileLogger extends Logger {
    protected static $instances = []; // 覆盖父类的静态属性
}

总结

  • self:: 是早期静态绑定,编译时确定
  • static:: 是后期静态绑定,运行时确定
  • instanceof self 检查定义类的实例
  • instanceof static 检查当前调用类的实例

在单例模式实现中,通常希望子类有自己的单例实例,因此推荐使完全后期静态绑定的版本:

if (!isset(static::$instance[static::class]) || !(static::$instance[static::class] instanceof static))

扩展:

PHP - 静态方法与非静态方法的区别[含性能,内存消耗] - 学习/实践

PHP 自己实现一个单例模式 给出完整的代码

PHP 单例模式完整实现

下面我将给出 PHP 中单例模式的完整实现,包含多种变体和最佳实践。

基础单例模式实现

class Singleton
{
    /**
     * @var Singleton 保存类实例的静态变量
     */
    private static $instance;

    /**
     * 私有构造函数,防止外部实例化
     */
    private function __construct()
    {
        // 初始化代码
    }

    /**
     * 私有克隆方法,防止克隆实例
     */
    private function __clone()
    {
    }

    /**
     * 私有反序列化方法,防止反序列化创建新实例
     */
    private function __wakeup()
    {
    }

    /**
     * 获取单例实例
     * 
     * @return Singleton
     */
    public static function getInstance(): Singleton
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    /**
     * 示例方法
     */
    public function doSomething(): void
    {
        echo "Singleton is working...\n";
    }
}

// 使用示例
$singleton = Singleton::getInstance();
$singleton->doSomething();

// 测试单例
$anotherInstance = Singleton::getInstance();
var_dump($singleton === $anotherInstance); // 输出: bool(true)

支持继承的多态单例模式

abstract class Multiton
{
    /**
     * @var array 保存各个子类实例
     */
    private static $instances = [];

    /**
     * 私有构造函数
     */
    private function __construct()
    {
    }

    /**
     * 获取当前类的单例实例
     * 
     * @return static
     */
    public static function getInstance()
    {
        $className = static::class;
        
        if (!isset(self::$instances[$className])) {
            self::$instances[$className] = new static();
        }

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

    /**
     * 防止克隆
     */
    private function __clone()
    {
    }

    /**
     * 防止反序列化
     */
    private function __wakeup()
    {
    }
}

class Logger extends Multiton
{
    public function log(string $message): void
    {
        echo "Logging: $message\n";
    }
}

class Config extends Multiton
{
    private $settings = [];

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

    public function get(string $key)
    {
        return $this->settings[$key] ?? null;
    }
}

// 使用示例
$logger1 = Logger::getInstance();
$logger2 = Logger::getInstance();
var_dump($logger1 === $logger2); // true

$config = Config::getInstance();
$config->set('debug', true);

线程安全的单例模式(适用于PHP-FPM等环境)

class ThreadSafeSingleton
{
    private static $instance;
    private static $lock = false;

    private function __construct()
    {
        // 初始化代码
    }

    public static function getInstance(): ThreadSafeSingleton
    {
        if (null === static::$instance && !static::$lock) {
            static::$lock = true;
            
            try {
                static::$instance = new static();
            } finally {
                static::$lock = false;
            }
        }

        return static::$instance;
    }

    private function __clone()
    {
    }

    private function __wakeup()
    {
    }
}

支持依赖注入的单例模式

class DIContainer
{
    private static $instance;
    private $services = [];

    private function __construct()
    {
    }

    public static function getInstance(): DIContainer
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    public function register(string $name, callable $resolver): void
    {
        $this->services[$name] = $resolver;
    }

    public function resolve(string $name)
    {
        if (!isset($this->services[$name])) {
            throw new \RuntimeException("Service $name not found");
        }

        return call_user_func($this->services[$name]);
    }
}

// 使用示例
$container = DIContainer::getInstance();
$container->register('db', function() {
    return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
});

$db = $container->resolve('db');

单例模式最佳实践

  • 避免全局状态:单例本质上是全局状态,应谨慎使用
  • 考虑依赖注入:优先考虑依赖注入而非直接使用单例
  • 测试友好设计:
class Singleton
{
    private static $instance;
    private static $testingInstance = null;

    public static function setTestingInstance($instance): void
    {
        self::$testingInstance = $instance;
    }

    public static function getInstance()
    {
        if (self::$testingInstance !== null) {
            return self::$testingInstance;
        }

        if (null === self::$instance) {
            self::$instance = new static();
        }

        return self::$instance;
    }
}
  • PSR-11 容器:考虑使用 psr/container 实现而非自制单例

单例模式在框架中的应用

Laravel 中的单例绑定

// 服务容器中注册单例
app()->singleton('cache', function() {
    return new CacheManager();
});

// 使用
$cache = app('cache');

Symfony 中的单例服务

# config/services.yaml
services:
    App\Service\SingletonService:
        shared: true  # 单例模式

总结

单例模式的核心要点:

  1. 私有构造函数防止外部实例化
  2. 静态方法获取唯一实例
  3. 防止克隆和反序列化
  4. 根据需求选择基础单例或多态单例
  5. 在现代化PHP开发中,通常使用依赖注入容器替代原生单例模式

PHP 单例模式核心要点详解

下面我将详细解释单例模式的五个核心要点及其背后的设计原理:

1. 私有构造函数防止外部实例化

实现方式:

private function __construct()
{
    // 初始化代码
}

原因:

  • 控制实例化过程:确保只能通过指定的静态方法创建实例
  • 避免随意创建:防止其他开发人员直接使用 new 关键字创建多个实例
  • 强制使用单例接口:必须通过 getInstance() 方法获取实例
  • 设计意图明确:通过语法限制明确表达这是单例类

反例说明: 如果构造函数是公开的:

$instance1 = new Singleton();
$instance2 = new Singleton();
// $instance1 !== $instance2,违背单例原则

2. 静态方法获取唯一实例

实现方式:

public static function getInstance()
{
    if (null === static::$instance) {
        static::$instance = new static();
    }
    return static::$instance;
}

原因:

  • 全局访问点:提供统一的实例获取途径
  • 延迟初始化:首次访问时才创建实例(节省资源)
  • 实例控制:在方法内实现实例存在性检查
  • 线程安全控制点:可以在该方法内添加同步锁(虽然PHP通常不需要)

关键特性:

$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();
// $singleton1 === $singleton2 保证唯一性

3. 防止克隆和反序列化

实现方式:

private function __clone()
{
    // 防止克隆
}

private function __wakeup()
{
    // 防止反序列化
}

原因:

  • 防止克隆:
  1. clone 关键字会创建对象副本
  2. 私有化 __clone() 方法会在尝试克隆时抛出错误
  • 防止反序列化:
  1. 序列化/反序列化会创建新对象
  2. PHP 的 unserialize() 会调用 __wakeup()
  3. 私有化 __wakeup() 可防止通过这种方式创建新实例

漏洞演示: 如果没有这些防护:

$singleton = Singleton::getInstance();
$clone = clone $singleton; // 创建了副本
$serialized = serialize($singleton);
$another = unserialize($serialized); // 又创建了新实例

4. 基础单例 vs 多态单例

基础单例

class Singleton {
    private static $instance;
    
    public static function getInstance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

多态单例(支持继承)

class Multiton {
    private static $instances = [];
    
    public static function getInstance() {
        $cls = static::class; // 后期静态绑定
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new static();
        }
        return self::$instances[$cls];
    }
}

class Child extends Multiton {}

选择依据:

  • 基础单例:当确定不需要继承时使用,更简单直接
  • 多态单例:当需要单例行为可继承时使用
  • 每个子类有自己的单例实例
  • 适用于框架基础类等场景

5. 依赖注入容器替代原生单例

现代PHP框架的做法:

// Laravel 的服务容器示例
app()->singleton('cache', function() {
    return new RedisCache();
});

// 使用
$cache = app('cache'); // 总是返回同一个实例

为什么推荐依赖注入容器:

  • 更灵活的 lifecycle 控制:

不只是单例,还可以控制为:

  1. 每次解析新实例(绑定为普通方式)
  2. 请求生命周期单例(Laravel 的 scoped
  3. 上下文相关实例

  • 更好的可测试性:
// 测试时可以替换实现
$mockCache = Mockery::mock(CacheInterface::class);
app()->instance('cache', $mockCache);

  • 解耦依赖关系:
  1. 不直接依赖具体单例类
  2. 通过接口和容器解析依赖

  • 配置集中管理:
// 在服务提供者中配置
public function register()
{
    $this->app->singleton(Database::class, function() {
        return new Database(config('database'));
    });
}

  • 支持自动注入:
class UserController {
    public function __construct(protected Database $db) {}
    // 容器会自动注入已注册的单例
}

传统单例模式的问题:

  • 硬编码依赖:Database::getInstance() 使类与具体实现紧密耦合
  • 难以模拟:在测试中难以替换单例实例
  • 全局状态:使应用状态难以追踪和管理
  • 缺乏灵活性:无法根据需要改变生命周期

总结对比

特性

传统单例

依赖注入容器

实例控制

类自身控制

容器集中控制

可测试性

灵活性

生命周期

仅单例

多种生命周期

耦合度

高耦合

低耦合

适用场景

简单应用

中大型应用

最终建议:

  • 小型工具类或确定不需要扩展的场合:使用传统单例
  • 应用核心组件或可能扩展的类:使用依赖注入容器
  • 框架开发:优先考虑容器绑定方式

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐