Clases estáticas en PHP a través de palabra clave abstracta?

De acuerdo con el manual de PHP , una clase como esta:

abstract class Example {} 

no puede ser instanciado. Si necesito una clase sin instancia, por ejemplo, para un patrón de registro:

 class Registry {} // and later: echo Registry::$someValue; 

¿se consideraría un buen estilo simplemente declarar la clase como abstracta? Si no, ¿cuáles son las ventajas de ocultar el constructor como método protegido en comparación con una clase abstracta?

Justificación para preguntar: Hasta donde yo lo veo, podría haber un poco de abuso de características, ya que el manual se refiere a clases abstractas más como planos para clases posteriores con posibilidad de instanciación.

Actualización: Antes que nada, ¡gracias por todas las respuestas! Pero muchas respuestas suenan bastante parecidas: ‘No se puede crear una instancia de una clase abstracta, sino de un registro, ¿por qué no se usa un patrón singleton?’

Desafortunadamente, eso fue más o menos exactamente una repetición de mi pregunta. ¿Cuál es la ventaja de usar un patrón singleton (también conocido como ocultar __construct() ) en comparación con simplemente declararlo abstract y no tener que preocuparse por eso? (Como, por ejemplo, es una fuerte connotación entre los desarrolladores, que las clases abstract no se usan realmente o eso).

Si su clase no pretende definir algún super-tipo, no debería declararse como abstract , diría yo.

En tu caso, prefiero ir con una clase:

  • Eso define __construct y __clone como métodos privados
    • entonces la clase no puede ser instanciada desde afuera
  • Y, de esta manera, tu clase podría crear una instancia de sí misma
    • Ver el patrón de diseño de Singleton , sobre eso, por cierto

Ahora, ¿por qué usar un Singleton, y no solo métodos estáticos? Supongo que, al menos un par de razones pueden ser válidas:

  • Usar un singleton significa usar una instancia de la clase; hace que sea más fácil transformar una clase que no sea singleton a una singleton one: solo tiene que hacer __construct y __clone private, y agregar algún método getInstance .
  • Usar un singleton también significa que tienes acceso a todo lo que puedes usar con una instancia normal: $this , properties, …
  • Ah, un tercero (no estoy seguro de eso, pero podría tener su importancia) : con PHP <5.3, tiene menos posibilidades con métodos / datos estáticos:
    • __callStatic solo se introdujo en PHP 5.3
    • No hay __getStatic , __setStatic , …
    • ¡Lo mismo para un par de otros métodos mágicos !
  • La unión estática tardía solo se ha agregado con PHP 5.3; y no tenerlo a menudo lo hace más difícil, cuando se trabaja con métodos / clases estáticos; especialmente cuando usas herencia.

Dicho esto, sí, algunos códigos como este:

 abstract class MyClass { protected static $data; public static function setA($a) { self::$data['a'] = $a; } public static function getA() { return self::$data['a']; } } MyClass::setA(20); var_dump(MyClass::getA()); 

Funcionará … Pero no se siente del todo natural … y este es un ejemplo muy simple (mira lo que dije antes con Late Static Binding y métodos mágicos) .

Lo que describes está permitido por el lenguaje PHP, pero no es el uso previsto de una clase abstract . No usaría métodos static de una clase abstract .

Esta es la desventaja de hacer eso: otro desarrollador podría extender su clase abstracta y luego crear una instancia de un objeto, que es lo que quiere evitar. Ejemplo:

 class MyRegistry extends AbstractRegistry { } $reg = new MyRegistry(); 

Es cierto, solo debe preocuparse por esto si le está entregando su clase abstracta a otro desarrollador que no cumplirá con su uso previsto, pero es por eso que también haría que la clase sea única. Un desarrollador no cooperativo puede anular un constructor privado:

 class Registry { private function __construct() { } } class MyRegistry extends Registry { public function __construct() { } // change private to public } 

Si estuviera usando esta clase usted mismo, simplemente recordaría no crear instancias de la clase. Entonces no necesitarías ninguno de los mecanismos para prevenirlo. Entonces, como está diseñando esto para ser usado por otros, necesita alguna manera de evitar que esas personas eludan su uso previsto.

Entonces ofrezco estas dos alternativas posibles:

  1. Sigue con el patrón singleton y asegúrate de que el constructor también sea final para que nadie pueda extender tu clase y cambiar el constructor a no privado:

     class Registry { private final function __construct() { } } 
  2. Haga que su Registro sea compatible tanto con el uso estático como con el objeto:

     class Registry { protected static $reg = null; public static function getInstance() { if (self::$reg === null) { self::$reg = new Registry(); } return self::$reg; } } 

    Luego puede llamar a Registry::getInstance() estáticamente, o puede llamar a new Registry() si desea una instancia de objeto.

    ¡Entonces puede hacer cosas ingeniosas como almacenar una nueva instancia de registro dentro de su registro global! 🙂

    Implementé esto como parte de Zend Framework, en Zend_Registry

Como otros chicos dijeron, no puedes instanciar una clase abstracta. Puede usar métodos estáticos en su clase para evitar crear instancias, pero realmente no soy partidario de hacerlo a menos que tenga una razón válida.

Puede que esté un poco fuera de tema ahora, pero en su ejemplo, dijo que quería esto para la clase de patrón de registro. ¿Cuál es la razón por la que no quieres crear una instancia? ¿No sería mejor crear una instancia de Registro para cada registro que quiera usar?

Algo como:

 class Registry { private $_objects = array( ); public function set( $name, $object ) { $this->_objects[ $name ] = $object; } public function get( $name ) { return $this->_objects[ $name ]; } } 

Ni siquiera usaría Singleton en este caso.

Configurar una clase para abstraer que solo define propiedades / métodos estáticos no tendrá un efecto real. Puede extender la clase, crear instancias y llamar a un método y cambiar las propiedades de clase estáticas. Obviamente muy confuso.

El resumen también es engañoso. Resumen es ment para definir una clase que implementa alguna funcionalidad, pero necesita más comportamiento (agregado a través de la herencia) para funcionar correctamente. Además de eso, suele ser una característica que no debe utilizarse con la estática en absoluto. Prácticamente estás invitando a los progtwigdores a usarlo incorrectamente.

Respuesta corta: un constructor privado sería más expresivo y seguro.

Hay patrones en OO que son comunes y bien reconocidos. Usar el abstract de una manera no convencional puede causar confusión (lo siento, algunos ejemplos están en Java en lugar de PHP):

  • clase abstracta : una clase destinada a conceptualizar un ancestro común, pero cuyas instancias reales no están destinadas a existir (por ejemplo, la forma es una superclase abstracta para rectángulo y triángulo).
    Comúnmente implementado por:
    • use abstract modificador abstract en la clase para evitar la creación de instancias directas, pero permita derivar de la clase
  • clase de utilidad : una clase que no representa un objeto en el espacio de la solución, sino que es una colección de operaciones / métodos estáticos útiles, por ejemplo, la clase de Matemáticas en Java.
    Comúnmente implementado por:
    • hacer que la clase no sea derivable, por ejemplo, Java usa el modificador final en la clase, y
    • previene la creación de instancias directas: no proporcione constructores y oculte o deshabilite los constructores implícitos o predeterminados (y los constructores de copias)
  • clase singleton : una clase que representa un objeto en el espacio de la solución, pero cuya creación de instancias está controlada o limitada, a menudo para asegurar que solo haya una instancia.
    Comúnmente implementado por:
    • hacer que la clase no sea derivable, por ejemplo, Java usa el modificador final en la clase, y
    • evitar la creación de instancias directas: no proporcione constructores y oculte o deshabilite los constructores implícitos o predeterminados (y los constructores de copias), y
    • proporcionar un medio específico para adquirir una instancia: un método estático (a menudo getInstance() ) que devuelve la única instancia o una de las pocas instancias

abstract realidad está destinado a indicar un “modelo”, como usted lo llama, para la herencia de clase.

Los registros generalmente siguen un patrón único, lo que significa que se crearía una instancia en una variable privada. Definirlo como abstract evitaría que esto funcione.

No usaría una clase abstracta. Id usar algo más parecido a un singleton con un constructor privado / protegido como sugieres. Debe haber muy pocas propiedades estáticas distintas de $instance que es la instancia de registro real. Recientemente me he convertido en fan del patrón típico de Zend Frameworks, que es algo como esto:

 class MyRegistry { protected static $instance = null; public function __construct($options = null) { } public static function setInstance(MyRegistry $instance) { self::$instance = $instance; } public static function getInstance() { if(null === self::$instance) { self::$instance = new self; } return self::$instance; } } 

De esta forma obtienes un singleton esencialmente pero puedes inyectar una instancia configurada para usar. Esto es útil para fines de prueba y herencia.

El propósito de una clase abstracta es definir métodos que son 1) significativos para otras clases y 2) no significativos cuando no están en el contexto de una de esas clases.

Parafraseando algunos de los documentos php, imagina que te estás conectando a una base de datos. Conectarse a una base de datos no tiene mucho sentido a menos que tenga un tipo particular de base de datos para conectarse. Sin embargo, conectar es algo que querrás hacer, independientemente del tipo de base de datos. Por lo tanto, la conexión puede definirse en una clase de base de datos abstracta y heredarse, y hacerse significativa por, digamos, una clase MYSQL.

Según sus requisitos, parece que no tiene la intención de hacerlo, sino que simplemente requiere una clase sin una instancia. Aunque podría usar una clase abstracta para hacer cumplir este comportamiento, esto me parece raro porque abusa del propósito de las clases abstractas. Si encuentro una clase abstracta, podría razonablemente esperar que esto tenga algunos métodos abstractos, por ejemplo, pero su clase no tendrá ninguno.

Por lo tanto, un singleton parece ser una mejor opción.

Sin embargo, si la razón por la que desea tener una clase sin una instancia es simplemente para poder llamarla a cualquier parte, ¿por qué tiene una clase? ¿Por qué no simplemente cargar cada variable como global y luego simplemente llamarla directamente en lugar de hacerlo a través de la clase?

Creo que la mejor manera de hacerlo es crear una instancia de la clase y luego pasarla con la dependency injection. Si eres demasiado perezoso para hacer eso (¡y es justo si lo eres! Es tu código, no el mío.) Entonces no te molestes con la clase en absoluto.

ACTUALIZACIÓN: parece que tiene conflictos entre dos necesidades: la necesidad de hacer las cosas rápidamente y la necesidad de hacer las cosas de la manera correcta. Si no le importa llevar una tonelada de variables globales por el bien de ahorrar tiempo, supuestamente preferirá usar el resumen en lugar de un singleton porque implica menos tipeo. Elija qué necesidad es más importante para usted y quédese con ella.

El camino correcto aquí es definitivamente no utilizar un Singleton o una clase abstracta y en su lugar usar la dependency injection. La manera más rápida es tener una tonelada de globales o una clase abstracta.

Desde mi punto de vista, una clase sin instancia es algo que no deberías usar en un progtwig OOP, porque el objective completo (y único) de las clases es servir como planos para objetos nuevos. La única diferencia entre Registry::$someValue y $GLOBALS['Registry_someValue'] es que el primero se ve ‘más elegante’, pero ninguna de las dos maneras está realmente orientada a objetos.

Por lo tanto, para responder a su pregunta, no desea una “clase singleton”, desea un objeto singleton, opcionalmente equipado con un método de fábrica:

 class Registry { static $obj = null; protected function __construct() { ... } static function object() { return self::$obj ? self::$obj : self::$obj = new self; } } ... Registry::object()->someValue; 

Claramente abstract no funcionará aquí.

Yo diría que es una cuestión de encoding de hábitos. Cuando piensas en una clase abstracta, generalmente es algo que debes subclasificar para usar. Entonces, declarar el resumen de tu clase no es intuitivo.

Aparte de eso, solo es cuestión de usar self :: $ somevar en sus métodos si lo hace abstracto, en lugar de $ this-> somevar si lo implementa como singleton.