2010-07-13 6 views
12

Por lo tanto, siempre me ha implementado un conjunto unitario de este modo:Implementar un singleton de PHP: propiedades de clases estáticas o variables de métodos estáticos?

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

Sin embargo, recientemente se me ocurrió que yo también podría ponerlo en práctica con variables estáticas miembros en cuanto a:

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() { } 
} 

Para mí, esto es más limpio porque no satura la clase , y no tengo que hacer ninguna verificación explícita de existencia, pero como nunca he visto esta implementación en ningún otro lado, me pregunto:

¿Hay algo de malo con el uso de la segunda implementación sobre la primera?

+0

El segundo enfoque también se puede implementar como una función normal, yo uso esto a veces, por ejemplo 'i() -> DB()'. –

+1

+1 buena pregunta y respuestas interesantes – NikiC

+0

@Alix: No sigo. ¿Cómo podría implementarse la función 'getInstance' de forma no estática y aún pertenecer a singletons? –

Respuesta

5

Probablemente decir que con una ligera modificación (Tengo un error de sintaxis lo contrario):

<?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'); 

Esto da:

ejemplo: (refcount = 2, is_ref = 1), objeto ( Singleton) []

a: (refcount = 1, is_ref = 0), objeto ( Singleton) []

ejemplo: (refcount = 2, is_ref = 1), objeto ( Singleton) []

b: (Refcount = 1, is_ref = 0), objeto ( Singleton) []

Por lo tanto, tiene la desventaja de un nuevo zval será creado en cada llamada. Esto no es particularmente serio, así que si lo prefieres, adelante.

La razón una separación zval es forzado es que dentro de getInstance, $instance es una referencia (en el sentido de =&, y tiene recuento de referencia 2 (uno para el símbolo dentro del método, otro para el almacenamiento estático). Desde getInstance no devuelve por referencia, el zval debe estar separado - para la devolución, se crea uno nuevo con el recuento de referencia 1 y el indicador de referencia claro

+0

Sí, olvidé que no puedes hacer no puede complejas asignaciones estáticas ... Pero no sigo: ¿qué es un zval? –

+0

@Austin Consulte http://php.net/manual/en/features.gc.refcounting-basics.php – Artefacto

+0

Ok, eso tiene más sentido. Pero en PHP 5, ¿los objetos no pasan por referencia por defecto? –

0

Después de jugar el mejor método que puedo pensar es como ese:

¡Cree un archivo llamado SingletonBase.php e inclúyalo en la raíz de su script!

El código es

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; 
    } 
} 

Entonces, para cualquier clase que desea hacer un producto único sólo tiene que añadir este pequeño método único.

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

Aquí hay un pequeño ejemplo:

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(); 

y se puede simplemente añadir esta función Singleton en cualquier clase que tiene y que sólo creará 1 instancia por clase.

Sólo otra idea también se puede hacer

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

y al final de la secuencia de comandos se puede hacer un poco de dfebugging si necesita también,

al final de la secuencia de comandos que pueda solo haga

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

por lo que este método será ideal para que un depurador tenga acceso a todas las clases y objetos datos que se han inicializado

+0

Buena idea, algo en lo que no había pensado. Sin embargo, esto realmente no responde mi pregunta. –

+0

su pregunta fue respondida por muchas otras personas con respecto a que usted no puede llamarlo singleton porque su creación de una nueva instancia cada vez, Mi código era para mostrarle cómo puede estar limpio, una pequeña cantidad de codificación, y actúa como un verdadero singleton, bruja, de hecho, responde partes de tu pregunta :) – RobertPitt

+1

Esto no crea un singleton porque aún puedes llamar 'new Database();' –

9

Ir con una propiedad de clase. Hay algunas ventajas ...

class Foo { 
    protected static $instance = null; 

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

En primer lugar, es más fácil realizar pruebas automatizadas. Puede crear una clase foo simulacro de "reemplazar" la instancia para que otras clases que dependen de foo recibirán una copia de la maqueta en lugar del original:

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

Luego, en los casos de prueba (suponiendo PHPUnit) :

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

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

Esto soluciona una queja común con singletons que son difíciles de probar.

En segundo lugar, hace que su código sea más flexible. Si alguna vez quiere "reemplazar" la funcionalidad en tiempo de ejecución en esa clase, todo lo que necesita hacer es crear una subclase y reemplazar self::$instance.

En tercer lugar, le permite operar en la instancia en otra función estática. Esto no es un gran problema para clases de instancia única (un singleton verdadero) ya que solo puede llamar al self::instance(). Pero si tiene varias copias "nombradas" (por ejemplo, para conexiones de bases de datos u otros recursos en los que desea más de uno, pero no desea crear uno nuevo si ya existen), se ensucia porque necesita realizar un seguimiento de los nombres:

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 
    } 
} 

Otra nota, no haría el constructor privado. Hará imposible extender o probar apropiadamente. En cambio, hágalo protegido para que pueda subclase si es necesario y aún así operar en el principal ...

+0

. Usted hace algunos buenos comentarios. ¡Gracias! –

+0

¿Por qué o por qué hiciste 'instancia' devolver por referencia.Esto le permite reemplazar externamente la instancia singleton, que no debería poder. De otra manera, te hubiera votado de otra manera. – Artefacto

+0

@Artefacto: buen punto. Fijo... – ircmaxell

Cuestiones relacionadas