【问题标题】:Extending singletons in PHP在 PHP 中扩展单例
【发布时间】:2011-03-08 18:07:51
【问题描述】:

我在一个 web 应用程序框架中工作,其中一部分由许多服务组成,所有服务都实现为单例。它们都扩展了一个 Service 类,其中实现了单例行为,看起来像这样:

class Service {
    protected static $instance;

    public function Service() {
        if (isset(self::$instance)) {
            throw new Exception('Please use Service::getInstance.');
        }
    }

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

现在,如果我有一个像这样实现的名为 FileService 的类:

class FileService extends Service {
    // Lots of neat stuff in here
}

...调用 FileService::getInstance() 不会像我想要的那样产生 FileService 实例,而是一个 Service 实例。我认为这里的问题是服务构造函数中使用的“self”关键字。

还有其他方法可以在这里实现我想要的吗?单例代码只有几行,但我仍然希望尽可能避免任何代码冗余。

【问题讨论】:

    标签: php inheritance singleton anti-patterns


    【解决方案1】:

    代码:

    abstract class Singleton
    {
        protected function __construct()
        {
        }
    
        final public static function getInstance()
        {
            static $instances = array();
    
            $calledClass = get_called_class();
    
            if (!isset($instances[$calledClass]))
            {
                $instances[$calledClass] = new $calledClass();
            }
    
            return $instances[$calledClass];
        }
    
        final private function __clone()
        {
        }
    }
    
    class FileService extends Singleton
    {
        // Lots of neat stuff in here
    }
    
    $fs = FileService::getInstance();
    

    如果你使用 PHP

    // get_called_class() is only in PHP >= 5.3.
    if (!function_exists('get_called_class'))
    {
        function get_called_class()
        {
            $bt = debug_backtrace();
            $l = 0;
            do
            {
                $l++;
                $lines = file($bt[$l]['file']);
                $callerLine = $lines[$bt[$l]['line']-1];
                preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l]['function'].'/', $callerLine, $matches);
            } while ($matches[1] === 'parent' && $matches[1]);
    
            return $matches[1];
        }
    }
    

    【讨论】:

    • 仅供参考:此代码使用get_called_class,在 PHP 5.3 中添加。在早期版本中执行此操作有点棘手。
    • 天哪,太可怕了。想象一下调用getInstance 十几次,即类文件的十几次打开和十几次读取。
    • 这就是为什么人们应该升级到最新最好的^^
    • 谢谢!在发布问题后,我实际上正在查看此功能,但我不确定如何使用它来解决问题。现在我只需要等到网络酒店的人升级到 PHP5.3 :)
    • @Johan 您可能想考虑完全放弃单身人士。它们将硬耦合引入您的应用程序并且难以测试。您可以使用依赖注入框架或注册表来解决可能只有一个实例的问题。
    【解决方案2】:

    如果我在 5.3 课上多加注意,我就会知道如何自己解决这个问题。使用 PHP 5.3 新的后期静态绑定特性,我相信 Coronatus 的提议可以简化为:

    class Singleton {
        protected static $instance;
    
        protected function __construct() { }
    
        final public static function getInstance() {
            if (!isset(static::$instance)) {
                static::$instance = new static();
            }
    
            return static::$instance;
        }
    
        final private function __clone() { }
    }
    

    我试过了,它就像一个魅力。不过,Pre 5.3 仍然是一个完全不同的故事。

    【讨论】:

    • 所有子类似乎只有一个字段$instance,所以只返回首先调用getInstance()的类的单例。
    • 这是一种幼稚的方法,不幸的是,正如 C-Otto 已经提到的那样,它根本不起作用。否决:不要在家里这样做;-)
    • 正如他们上面所说的......这是错误的,这将为您提供第一个使用 getInstance() 的类的实例
    【解决方案3】:

    这是固定约翰的答案。 PHP 5.3+

    abstract class Singleton
    {
        protected function __construct() {}
        final protected function __clone() {}
    
        final public static function getInstance()
        {
            static $instance = null;
    
            if (null === $instance)
            {
                $instance = new static();
            }
    
            return $instance;
        }
    }
    

    【讨论】:

      【解决方案4】:

      我找到了一个很好的解决方案。

      以下是我的代码

      abstract class Singleton
      {
          protected static $instance; // must be protected static property ,since we must use static::$instance, private property will be error
      
          private function __construct(){} //must be private !!! [very important],otherwise we can create new father instance in it's Child class 
      
          final protected function __clone(){} #restrict clone
      
          public static function getInstance()
          {
              #must use static::$instance ,can not use self::$instance,self::$instance will always be Father's static property 
              if (! static::$instance instanceof static) {
                  static::$instance = new static();
              }
              return static::$instance;
          }
      }
      
      class A extends Singleton
      {
         protected static $instance; #must redefined property
      }
      
      class B extends A
      {
          protected static $instance;
      }
      
      $a = A::getInstance();
      $b = B::getInstance();
      $c = B::getInstance();
      $d = A::getInstance();
      $e = A::getInstance();
      echo "-------";
      
      var_dump($a,$b,$c,$d,$e);
      
      #object(A)#1 (0) { }
      #object(B)#2 (0) { } 
      #object(B)#2 (0) { } 
      #object(A)#1 (0) { } 
      #object(A)#1 (0) { }
      

      您可以参考http://php.net/manual/en/language.oop5.late-static-bindings.php 了解更多信息

      【讨论】:

      • 这是从哪个版本支持的?
      【解决方案5】:

      使用 trait 代替抽象类允许扩展单例类。

      将特征 SingletonBase 用于父单例类。

      将特征 SingletonChild 用于其单例子节点。

      interface Singleton
      {
      
          public static function getInstance(): Singleton;
      
      }
      
      trait SingletonBase
      {
      
          private static $instance=null;
      
          abstract protected function __construct();
      
          public static function getInstance(): Singleton {
      
             if (is_null(self::$instance)) {
      
                self::$instance=new static();
      
             }
      
             return self::$instance;
      
          } 
      
          protected function clearInstance(): void {
      
              self::$instance=null;
      
          }
      
          public function __clone()/*: void*/ {
      
              trigger_error('Class singleton '.get_class($this).' cant be cloned.');
          }
      
          public function __wakeup(): void {
      
              trigger_error('Classe singleton '.get_class($this).' cant be serialized.');
      
          }
      
      }
      
      trait SingletonChild
      {
      
          use SingletonBase;
      
      }
      
      class Bar
      {
      
          protected function __construct(){
      
          }
      
      }
      
      class Foo extends Bar implements Singleton
      {
      
            use SingletonBase;
      
      }
      
      class FooChild extends Foo implements Singleton
      {
      
            use SingletonChild; // necessary! If not, the unique instance of FooChild will be the same as the unique instance of its parent Foo
      
      }
      

      【讨论】:

      • 如果你用 2 个不同的对象扩展那个单例,它将用 SingletonBase 的最新子对象覆盖 self::$instance
      【解决方案6】:
          private static $_instances = [];
      
          /**
           * gets the instance via lazy initialization (created on first usage).
           */
          public static function getInstance():self
          {
              $calledClass = class_basename(static::class);
      
              if (isset(self::$_instances[$calledClass])) {
                  self::$_instances[$calledClass] = new static();
              }
      
              return self::$_instances[$calledClass];
          }
      

      这个唯一的问题是,如果你有同名的单身人士。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-20
        • 1970-01-01
        • 2015-11-03
        • 1970-01-01
        • 2013-06-10
        相关资源
        最近更新 更多