Cuándo usar clases estáticas vs instanciadas

170

PHP es mi primer lenguaje de programación. No puedo entender cuándo usar clases estáticas frente a objetos instanciados.

Me doy cuenta de que puedes duplicar y clonar objetos. Sin embargo, en todo mi tiempo usando php, cualquier objeto o función siempre terminaba como un solo valor de retorno (matriz, cadena, int) o vacío.

Entiendo conceptos en libros como una clase de personaje de videojuego. duplicar el objeto del automóvil y hacer que el nuevo sea rojo , todo tiene sentido, pero lo que no es su aplicación en php y aplicaciones web.

Un simple ejemplo. Un blog. ¿Qué objetos de un blog se implementarían mejor como objetos estáticos o instanciados? La clase DB? ¿Por qué no simplemente instanciar el objeto db en el ámbito global? ¿Por qué no hacer cada objeto estático en su lugar? ¿Qué pasa con el rendimiento?

¿Es todo solo estilo? ¿Hay una manera adecuada de hacer esto?

usuario73119
fuente

Respuestas:

123

Esta es una pregunta bastante interesante, y las respuestas también pueden ser interesantes ^^

La forma más sencilla de considerar las cosas podría ser:

  • use una clase instanciada donde cada objeto tenga datos por sí mismo (como un usuario tiene un nombre)
  • usa una clase estática cuando es solo una herramienta que funciona en otras cosas (como, por ejemplo, un convertidor de sintaxis para código BB a HTML; no tiene vida propia)

(Sí, lo admito, realmente simplificado demasiado ...)

Una cosa sobre los métodos / clases estáticos es que no facilitan las pruebas unitarias (al menos en PHP, pero probablemente también en otros lenguajes).

Otra cosa sobre los datos estáticos es que solo existe una instancia de ellos en su programa: si configura MyClass :: $ myData en algún valor, tendrá este valor, y solo este, en todas partes: hablando sobre el usuario, podrías tener un solo usuario, lo cual no es tan bueno, ¿verdad?

Para un sistema de blog, ¿qué podría decir? No hay mucho que escribiría como estático, en realidad, creo; quizás la clase DB-access, pero probablemente no, al final ^^

Pascal MARTIN
fuente
Entonces, ¿básicamente usa objetos cuando trabaja con cosas únicas como una foto, usuario, publicación, etc. y usa estática cuando está destinada a cosas generales?
Robert Rocha
1
Hablando bien del usuario, solo tiene un usuario activo en cada solicitud. por lo tanto, tener sentido al usuario activo de la solicitud estática tendría sentido
My1
69

Las dos razones principales contra el uso de métodos estáticos son:

  • el código usando métodos estáticos es difícil de probar
  • el código que usa métodos estáticos es difícil de extender

Tener una llamada a un método estático dentro de otro método es en realidad peor que importar una variable global. En PHP, las clases son símbolos globales, por lo que cada vez que llama a un método estático se basa en un símbolo global (el nombre de la clase). Este es un caso cuando global es malvado. Tuve problemas con este tipo de enfoque con algún componente de Zend Framework. Hay clases que usan llamadas a métodos estáticos (fábricas) para construir objetos. Fue imposible para mí suministrar otra fábrica a esa instancia para obtener un objeto personalizado devuelto. La solución a este problema es utilizar solo instancias y métodos de instancias y aplicar singletons y similares al comienzo del programa.

Miško Hevery , que trabaja como entrenador ágil en Google, tiene una teoría interesante, o más bien aconseja, que separemos el tiempo de creación del objeto del momento en que lo usamos. Entonces el ciclo de vida de un programa se divide en dos. La primera parte ( main()digamos el método), que se encarga de todo el cableado de objetos en su aplicación y la parte que hace el trabajo real.

Entonces, en lugar de tener:

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}

Más bien deberíamos hacer:

class HttpClient
{
    private $httpResponseFactory;

    public function __construct($httpResponseFactory)
    {
        $this->httpResponseFactory = $httpResponseFactory;
    }

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}

Y luego, en la página índice / principal, lo haríamos (este es el paso de cableado del objeto, o el momento de crear el gráfico de instancias que utilizará el programa):

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();

La idea principal es desacoplar las dependencias de tus clases. De esta forma, el código es mucho más extensible y, lo más importante para mí, comprobable. ¿Por qué es más importante ser comprobable? Debido a que no siempre escribo el código de la biblioteca, la extensibilidad no es tan importante, pero la comprobabilidad es importante cuando refactorizo. De todos modos, el código comprobable generalmente produce código extensible, por lo que no es realmente una situación de uno u otro.

Miško Hevery también hace una clara distinción entre singletons y Singletons (con o sin S mayúscula). La diferencia es muy simple. Los singletons con minúscula "s" se aplican mediante el cableado en el índice / principal. Instancia un objeto de una clase que no implementa el patrón Singleton y se asegura de pasar esa instancia a cualquier otra instancia que lo necesite. Por otro lado, Singleton, con una "S" mayúscula es una implementación del patrón clásico (anti). Básicamente, un disfraz global que no tiene mucho uso en el mundo PHP. No he visto uno hasta este punto. Si desea que todas sus clases utilicen una única conexión de base de datos, es mejor hacerlo así:

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);

Al hacer lo anterior, está claro que tenemos un singleton y también tenemos una buena manera de inyectar un simulacro o un trozo en nuestras pruebas. Es sorprendente cómo las pruebas unitarias conducen a un mejor diseño. Pero tiene mucho sentido cuando crees que las pruebas te obligan a pensar en la forma en que usarías ese código.

/**
 * An example of a test using PHPUnit. The point is to see how easy it is to
 * pass the UserCollection constructor an alternative implementation of
 * DbCollection.
 */
class UserCollection extends PHPUnit_Framework_TestCase
{
    public function testGetAllComments()
    {
        $mockedMethods = array('query');
        $dbMock = $this->getMock('DbConnection', $mockedMethods);
        $dbMock->expects($this->any())
               ->method('query')
               ->will($this->returnValue(array('John', 'George')));

        $userCollection = new UserCollection($dbMock);
        $allUsers       = $userCollection->getAll();

        $this->assertEquals(array('John', 'George'), $allUsers);
    }
}

La única situación en la que usaría (y los he usado para imitar el objeto prototipo de JavaScript en PHP 5.3) miembros estáticos es cuando sé que el campo respectivo tendrá el mismo valor de instancia cruzada. En ese punto, puede usar una propiedad estática y quizás un par de métodos getter / setter estáticos. De todos modos, no olvide agregar la posibilidad de anular el miembro estático con un miembro de instancia. Por ejemplo, Zend Framework estaba usando una propiedad estática para especificar el nombre de la clase de adaptador DB utilizada en instancias de Zend_Db_Table. Ha pasado un tiempo desde que los he usado, por lo que puede que ya no sea relevante, pero así es como lo recuerdo.

Los métodos estáticos que no tratan con propiedades estáticas deberían ser funciones. PHP tiene funciones y deberíamos usarlas.

Ionuț G. Stan
fuente
1
@ Ionut, no lo dudo un poco. Me doy cuenta de que también hay valor en el puesto / rol; sin embargo, como todo lo relacionado con XP / Agile, tiene un nombre realmente flojo.
jason
2
"Inyección de dependencia" Creo que es el término que suena demasiado complicado para esto. MUCHA y mucha lectura en todo SO y google-land. En cuanto a los "singletons", aquí hay algo que realmente me hizo pensar: slideshare.net/go_oh/… Una charla sobre por qué los singletons de PHP realmente no tienen ningún sentido. Espero que esto contribuya un poco a una vieja pregunta :)
dudewad
22

Entonces, en PHP, la estática se puede aplicar a funciones o variables. Las variables no estáticas están vinculadas a una instancia específica de una clase. Los métodos no estáticos actúan en una instancia de una clase. Así que inventemos una clase llamada BlogPost.

titleSería un miembro no estático. Contiene el título de esa publicación de blog. También podríamos tener un método llamado find_related(). No es estático porque requiere información de una instancia específica de la clase de publicación de blog.

Esta clase se vería así:

class blog_post {
    public $title;
    public $my_dao;

    public function find_related() {
        $this->my_dao->find_all_with_title_words($this->title);
    }
}

Por otro lado, usando funciones estáticas, puede escribir una clase como esta:

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}

En este caso, dado que la función es estática y no actúa en ninguna publicación de blog en particular, debe pasarla como argumento.

Fundamentalmente, esta es una pregunta sobre el diseño orientado a objetos. Sus clases son los sustantivos en su sistema, y ​​las funciones que actúan sobre ellos son los verbos. Las funciones estáticas son de procedimiento. Usted pasa el objeto de las funciones como argumentos.


Actualización: También agregaría que la decisión rara vez es entre métodos de instancia y métodos estáticos, y más entre usar clases y usar matrices asociativas. Por ejemplo, en una aplicación de blogs, puede leer publicaciones de blog de la base de datos y convertirlas en objetos, o dejarlas en el conjunto de resultados y tratarlas como matrices asociativas. Luego, escribe funciones que toman matrices asociativas o listas de matrices asociativas como argumentos.

En el escenario OO, escribe métodos en su BlogPostclase que actúan en publicaciones individuales, y escribe métodos estáticos que actúan en colecciones de publicaciones.

Rafe
fuente
44
Esta respuesta es bastante buena, especialmente con la actualización que hiciste, porque esa es la razón por la que terminé con esta pregunta. Entiendo la funcionalidad de "::" vs "$ this", pero cuando agrega a la cuenta, el ejemplo que ha dado de blogposts se extrae de una base de datos en una matriz asociativa. Agrega una dimensión completamente nueva, es decir También en el tono subyacente de esta pregunta.
Gerben Jacobs
Esta es una de las mejores respuestas prácticas (versus teóricas) a esta pregunta PHP muy solicitada, el apéndice es muy útil. ¡Creo que esta es la respuesta que muchos programadores de PHP están buscando, votaron!
ajmedway
14

¿Es todo solo estilo?

Un largo camino, sí. Puede escribir programas orientados a objetos perfectamente buenos sin usar miembros estáticos. De hecho, algunas personas argumentarían que los miembros estáticos son una impureza en primer lugar. Sugeriría que, como principiante en oop, intente evitar miembros estáticos por completo. Te obligará a escribir en un objeto orientado en lugar de un estilo de procedimiento .

Troelskn
fuente
13

Tengo un enfoque diferente para la mayoría de las respuestas aquí, especialmente cuando uso PHP. Creo que todas las clases deberían ser estáticas a menos que tenga una buena razón por la que no. Algunas de las razones del "por qué no" son:

  • Necesita varias instancias de la clase.
  • Tu clase necesita ser extendida
  • Partes de su código no pueden compartir las variables de clase con ninguna otra parte

Déjame tomar un ejemplo. Como cada script PHP produce código HTML, mi framework tiene una clase de escritor html. Esto garantiza que ninguna otra clase intentará escribir HTML, ya que es una tarea especializada que debe concentrarse en una sola clase.

Por lo general, usaría la clase html de esta manera:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

Cada vez que se llama a get_buffer (), restablece todo para que la próxima clase que use el escritor html comience en un estado conocido.

Todas mis clases estáticas tienen una función init () que debe llamarse antes de que la clase se use por primera vez. Esto es más por convención que una necesidad.

La alternativa a una clase estática en este caso es desordenada. No querrá que cada clase que necesita escribir un poco de html tenga que administrar una instancia del escritor html.

Ahora te daré un ejemplo de cuándo no usar clases estáticas. Mi clase de formulario administra una lista de elementos de formulario como entradas de texto, listas desplegables y más. Por lo general, se usa así:

$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class

No hay forma de que pueda hacer esto con clases estáticas, especialmente teniendo en cuenta que algunos de los constructores de cada clase agregada hacen mucho trabajo. Además, la cadena de herencia para todos los elementos es bastante compleja. Así que este es un claro ejemplo donde las clases estáticas no deberían usarse.

La mayoría de las clases de utilidad, como las cadenas de conversión / formateo, son buenas candidatas para ser una clase estática. Mi regla es simple: todo se vuelve estático en PHP a menos que haya una razón por la cual no debería.

JG Estiot
fuente
¿puede escribir un ejemplo del siguiente punto? "Las partes de su código no pueden compartir las variables de clase con ninguna otra parte" #JG Estiot
khurshed alam
10

"Tener una llamada a un método estático dentro de otro método es realmente peor que importar una variable global". (defina "peor") ... y "Los métodos estáticos que no tratan con propiedades estáticas deberían ser funciones".

Ambas son declaraciones bastante amplias. Si tengo un conjunto de funciones relacionadas con el tema, pero los datos de la instancia son totalmente inapropiados, preferiría tenerlos definidos en una clase y no cada uno de ellos en el espacio de nombres global. Solo estoy usando la mecánica disponible en PHP5 para

  • darles a todos un espacio de nombres, evitando cualquier conflicto de nombres
  • manténgalos físicamente ubicados juntos en lugar de dispersarse en un proyecto; otros desarrolladores pueden encontrar más fácilmente lo que ya está disponible y es menos probable que reinventen la rueda
  • déjame usar consts de clase en lugar de definiciones globales para cualquier valor mágico

Es una forma conveniente de forzar una mayor cohesión y un menor acoplamiento.

Y FWIW: no existe tal cosa, al menos en PHP5, como "clases estáticas"; Los métodos y propiedades pueden ser estáticos. Para evitar la creación de instancias de la clase, también se puede declarar abstracta.

Grantwparks
fuente
2
"Para evitar la creación de instancias de la clase, también se puede declarar abstracta". Me gusta mucho. Anteriormente leí sobre personas que hacen de __construct () una función privada, pero creo que si alguna vez lo necesito, probablemente usaré el resumen
Kavi Siegel
7

Primero pregúntese, ¿qué va a representar este objeto? Una instancia de objeto es buena para operar en conjuntos separados de datos dinámicos.

Un buen ejemplo sería ORM o capa de abstracción de base de datos. Puede tener múltiples conexiones de base de datos.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));

Esas dos conexiones ahora pueden operar de forma independiente:

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);

Ahora dentro de este paquete / biblioteca, puede haber otras clases como Db_Row, etc. para representar una fila específica devuelta desde una instrucción SELECT. Si esta clase Db_Row fuera una clase estática, supondría que solo tiene una fila de datos en una base de datos y sería imposible hacer lo que podría hacer una instancia de objeto. Con una instancia, ahora puede tener un número ilimitado de filas en un número ilimitado de tablas en un número ilimitado de bases de datos. El único límite es el hardware del servidor;).

Por ejemplo, si el método getRows en el objeto Db devuelve una matriz de objetos Db_Row, ahora puede operar en cada fila independientemente uno del otro:

foreach ($someRecordsFromDb1 as $row) {
    // change some values
    $row->someFieldValue = 'I am the value for someFieldValue';
    $row->anotherDbField = 1;

    // now save that record/row
    $row->save();
}

foreach ($someRecordsFromDb2 as $row) {
    // delete a row
    $row->delete();
}

Un buen ejemplo de una clase estática sería algo que maneje variables de registro o variables de sesión, ya que solo habrá un registro o una sesión por usuario.

En una parte de su solicitud:

Session::set('someVar', 'toThisValue');

Y en otra parte:

Session::get('someVar'); // returns 'toThisValue'

Dado que solo habrá un usuario a la vez por sesión, no tiene sentido crear una instancia para la sesión.

Espero que esto ayude, junto con las otras respuestas para ayudar a aclarar las cosas. Como nota al margen, verifique " cohesión " y " acoplamiento ". Esbozan algunas muy, muy buenas prácticas para usar al escribir su código que se aplican a todos los lenguajes de programación.

Tres
fuente
6

Si su clase es estática, eso significa que no puede pasar su objeto a otras clases (ya que no hay una instancia posible), lo que significa que todas sus clases usarán directamente esa clase estática, lo que significa que su código ahora está estrechamente unido a la clase .

El acoplamiento estricto hace que su código sea menos reutilizable, frágil y propenso a errores. Desea evitar las clases estáticas para poder pasar la instancia de la clase a otras clases.

Y sí, esta es solo una de las muchas otras razones, algunas de las cuales ya se han mencionado.

Muhammad Hasan Khan
fuente
3

En general, debe usar variables miembro y funciones miembro a menos que sea absolutamente necesario compartirlas entre todas las instancias o a menos que esté creando un singleton. El uso de datos de miembros y funciones de miembros le permite reutilizar sus funciones para múltiples datos diferentes, mientras que solo puede tener una copia de los datos en los que opera si usa datos y funciones estáticos. Además, aunque no es aplicable a PHP, las funciones estáticas y los datos hacen que el código no sea reentrante, mientras que los datos de clase facilitan la reentrada.

Michael Aaron Safyan
fuente
3

Me gustaría decir que definitivamente hay un caso en el que me gustaría tener variables estáticas en aplicaciones en varios idiomas. Podría tener una clase a la que le pase un idioma (por ejemplo, $ _SESSION ['idioma']) y, a su vez, accederá a otras clases diseñadas de la siguiente manera:

Srings.php //The main class to access
StringsENUS.php  //English/US 
StringsESAR.php  //Spanish/Argentina
//...etc

Usar Strings :: getString ("somestring") es una buena manera de abstraer el uso del idioma de su aplicación. Puede hacerlo como quiera, pero en este caso, tener cada archivo de cadenas con constantes con valores de cadena a los que acceda la clase Cuerdas funciona bastante bien.

amigo
fuente