代码之家  ›  专栏  ›  技术社区  ›  Austin Hyde

实现一个php单例:静态类属性还是静态方法变量?

  •  12
  • Austin Hyde  · 技术社区  · 15 年前

    所以,我一直在实现这样一个单例:

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

    然而,最近我突然想到,我也可以用成员级的静态变量来实现它:

    class Singleton {
        public static function getInstance() {
            //oops - can't assign expression here!
            static $instance = null; // = new Singleton();
            if ($instance === null) $instance = new Singleton();
            return $instance;
        }
        private function __construct() { }
    }
    

    对我来说,这更干净,因为它不会使课堂混乱。 我不需要做任何明确的存在检查 但是,因为我从未在其他地方看到过这种实现,所以我想知道:

    使用第二个实现而不是第一个实现有什么问题吗?

    4 回复  |  直到 11 年前
        1
  •  6
  •   Artefacto    15 年前

    您的意思可能是稍微修改一下(否则我会得到一个语法错误):

    <?php
    class Singleton {
        public static function getInstance() {
            static $instance;
            if ($instance === null)
                $instance = new Singleton();
            xdebug_debug_zval('instance');
            return $instance;
        }
        private function __construct() { }
    }
    $a = Singleton::getInstance();
    xdebug_debug_zval('a');
    $b = Singleton::getInstance();
    xdebug_debug_zval('b');
    

    这给出:

    实例: (refcount=2,is_ref=1) , 对象 ( 独生子女 ) ]

    答: (refcount=1,is_ref=0) , 对象 ( 独生子女 ) ]

    实例: (refcount=2,is_ref=1) , 对象 ( 独生子女 ) ]

    乙: (refcount=1,is_ref=0) , 对象 ( 独生子女 ) ]

    因此它有一个缺点:每次调用都会创建一个新的zval。这不是特别严重,所以如果你喜欢,就去吧。

    ZVAL分离被强制的原因是内部 getInstance , $instance 是一个参考(在意义上 =& ,它具有引用计数2(一个用于方法内部的符号,另一个用于静态存储)。自从 获取实例 不通过引用返回,zval必须分开--对于返回,将创建一个引用计数为1且引用标志清除的新值。

        2
  •  10
  •   ircmaxell    15 年前

    使用类属性。有一些优势…

    class Foo {
        protected static $instance = null;
    
        public static function instance() {
            if (is_null(self::$instance)) {
                self::$instance = new Foo();
            }
            return self::$instance;
        }
    }
    

    首先,执行自动化测试更容易。您可以创建一个模拟foo类来“替换”实例,以便依赖foo的其他类将获得模拟的副本,而不是原始的:

    class MockFoo extends Foo {
        public static function initialize() {
            self::$instance = new MockFoo();
        }
        public static function deinitialize() {
            self::$instance = null;
        }
    }
    

    然后,在您的测试用例中(假设phpunit):

    protected function setUp() {
        MockFoo::initialize();
    }
    
    protected function tearDown() {
        MockFoo::deinitialize();
    }
    

    这就绕过了单身汉的共同抱怨,他们很难测试。

    其次,它使代码更加灵活。如果您希望在该类的运行时“替换”该功能,您所需要做的就是将其子类化并替换 self::$instance .

    第三,它允许您在其他静态函数中对实例进行操作。对于单实例类(一个真正的单实例类),这不是什么大问题,因为您可以调用 self::instance() . 但是,如果您有多个“命名”副本(例如,对于需要多个副本的数据库连接或其他资源,但如果它们已经存在,则不希望创建新的副本),则会变脏,因为您需要跟踪名称:

    protected static $instances = array();
    
    public static function instance($name) {
        if (!isset(self::$instances[$name])) {
            self::$instances[$name] = new Foo($name);
        }
        return self::$instances[$name];
    }
    
    public static function operateOnInstances() {
        foreach (self::$instances as $name => $instance) {
            //Do Something Here
        }
    }
    

    另外一个注意事项是,我不会将构造函数设置为私有的。这将使其无法正确扩展或测试。相反,使它受到保护,这样您可以在需要时子类,并且仍然在父类上操作…

        3
  •  0
  •   gphilip    15 年前

    最干净的解决方案是从类本身删除单例逻辑(因为它与类本身的作业无关)。

    有关有趣的实现,请参见: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/

        4
  •  0
  •   BenMorel Manish Pradhan    11 年前

    在一些游戏之后,我能想到的最好的方法是这样的:

    创建一个名为singletonbase.php的文件,并将其包含在脚本的根目录中!

    代码是

    abstract class SingletonBase
    {
        private static $storage = array();
    
        public static function Singleton($class)
        {
            if(in_array($class,self::$storage))
            {
                return self::$storage[$class];
            }
            return self::$storage[$class] = new $class();
        }
        public static function storage()
        {
           return self::$storage;
        }
    }
    

    然后对于任何你想要创建一个单例的类,只需添加这个小的单例方法。

    public static function Singleton()
    {
        return SingletonBase::Singleton(get_class());
    }
    

    下面是一个小例子:

    include 'libraries/SingletonBase.resource.php';
    
    class Database
    {
        //Add that singleton function.
        public static function Singleton()
        {
            return SingletonBase::Singleton(get_class());
        }
    
        public function run()
        {
            echo 'running...';
        }
    }
    
    $Database = Database::Singleton();
    
    $Database->run();
    

    您可以在任何类中添加这个singleton函数,它将只为每个类创建一个实例。

    你也可以这么做

    if(class_exists('Database'))
    {
       $Database = SingletonBase::Singlton('Database');
    }
    

    在脚本的最后,如果你需要的话,你可以做一些数据挖掘,

    在脚本的末尾,您可以

    foreach(SingletonBase::storage () as $name => $object)
    {
         if(method_exists("debugInfo",$object))
         {
             debug_object($name,$object,$object->debugInfo());
         }
    }
    

    因此,对于调试器来说,该方法非常适合访问所有已初始化的类和对象状态。

    推荐文章