Programación Orientada a Objetos (II)

Seguimos en este artículo hablando de Programación Orientada a Objetos. Tal como te prometí, en este artículo vamos a empezar a ver ejemplos prácticos, basados en lo que hablamos en el artículo anterior, a fin de asentar conceptos de una forma eficaz, más allá de la mera cháchara teórica. La teoría está muy bien (y, de hecho, todos sabemos que es imprescindible) pero, si se queda en sólo teoría, sirve de nada o menos.

Para los ejemplos de este artículo (y de los sucesivos), vamos a emplear PHP ya que, como todos sabemos, es el preferido de la inmensa mayoría de los desarrolladores web… y no está sólo limitado al ámbito de la web. Además, para aquellos que no estéis familiarizados con él, es uno de los lenguajes de alto nivel más fáciles de aprender. Me viene a la memoria un adagio muy popular en el mundillo del póker. Se dice que es un juego que se tarda muy poco en aprender, y toda una vida en dominar. Algo parecido se puede decir de PHP, y de otros lenguajes. Aquí trataremos de hacértelo lo más digestivo posible, aunque, desde luego, para seguir estos artículos deberás tener, al menos, algunas nociones de PHP. Estos artículos no introducen en los fundamentos del lenguaje, sino que están destinados al aprendizaje de la OOP y buenas prácticas de programación. Si quieres introducirte en los conceptos básicos de PHP, te sugiero empezar por los artículos de este enlace.

También verás que, en estos primeros ejemplos, me salto algunos principios fundamentales de programación, detallados en este artículo. Eso es porque prefiero no forzar la situación, e ir introduciendo los conceptos poco a poco, para que se nos vayan quedando. En sucesivos artículos iremos subiendo de nivel.

CREANDO UNA CLASE

En este ejemplo vamos a ver cómo crear una clase, e instanciarla para crear objetos a partir de la misma. Vamos a crear una estructura de archivos muy simple, como se aprecia en la figura de la derecha. Cómo ves, la raíz del proyecto se llama primer_ejemplo y, dentro de ella, tenemos una archivo index.php y un directorio llamado src. Dentro de este último, tenemos la clase que emplearemos para crear objetos televisor, con el nombre CrearTelevisorClass.php.

Es por este último archivo por el que vamos a empezar. Mira el listado del mismo a continuación.

Lo primero que vemos es que el archivo tiene el mismo nombre que la clase que se declara en él. Además, vemos que en este archivo se declara la clase, y nada más. No hace nada más que declarar la clase. Esta es una de la buenas prácticas de programación. De esta forma, siempre sabemos qué es lo que hace cada archivo, y donde está cada cosa.

Cómo todas las normas, cada autor luego las «adapta» a una manera específica de trabajar. Por ejemplo, es fácil encontrar que dentro del archivo CrearTelevisorClass.php lo que hay es una clase que se declara como CrearTelevisor, sin el sufijo Class. O, al revés, que dentro del archivo CrearTelevisor.php se declare la clase CrearTelevisorClass. Personalmente, te aconsejo seguir las pautas de nomenclatura que te doy aquí.

Es muy habitual que, en proyectos más complejos, que incorporen varios archivos de clase, estos se encuentren en una ruta específica, como podría ser src/classes. Todo es cuestión de organizarse y, por supuesto, seguir la misma pauta en todo el proyecto. Lo que, desde luego, no puedes hacer nunca es ir cambiando tu modo de trabajo, y hacer unas cosas de una manera y otras de otra en el mismo proyecto, porque eso sólo sirve para inducir a confusión.

DECLARANDO LAS PROPIEDADES

Lo primero que vemos en el contenido de la declaración de la clase es que se declaran tres propiedades, que tendrá cada uno de los objetos de dicha clase. En nuestro ejemplo son el color de la carcasa, el tamaño del televisor, y el tipo de pantalla. Y aquí aparece un concepto interesante. Observa que las propiedades se declaran precediéndolas de la palabra private. Esto hace que, cuando instanciemos la clase (creemos un objeto a partir de la misma), estas propiedades no sean directamente accesibles desde fuera del objeto. Esto, que se conoce con el nombre de encapsulación, nos da un mayor control sobre las propiedades del objeto, evitando que se cambie su valor accidentalmente. Ahora suena un poco críptico ahora, lo sé. En cuanto veamos (en este mismo artículo) como instanciamos y usamos el objeto, lo entenderás claramente.

LOS MÉTODOS

Los métodos son, simplemente, funciones que son declaradas dentro de una clase, y son de uso exclusivo de dicha clase, o de los objetos de la misma. Al igual que las propiedades, pueden encapsularse (declarándolos cómo privados), o declararse como públicos. Una vez más, te digo lo mismo. Te dejaré claro el concepto de encapsulación un poco más adelante en este mismo artículo.

EL MÉTODO CONSTRUCTOR

En esta clase sólo hemos declarado un método. Además, es un método con un nombre un poco especial. __construct es una palabra reservada de PHP (y de algunos otros lenguajes, si a eso vamos) que define un método que no debe ser invocado desde un objeto, sino que se invoca internamente, de modo automático, cuando instanciamos un objeto. Su finalidad es inicializar las propiedades del objeto a partir de las propiedades definidas en la clase. Este método recibe los argumentos necesarios (si los hubiera) para asignárselos al objeto que está construyendo.

EL OBJETO $this

Cuando queremos actuar sobre una propiedad o un método de un objeto en PHP usamos la siguiente notación:

$objeto->propiedad

o bien, en el caso de métodos:

$objeto->metodo([argumentos, si los hay])

Cada objeto se referencia con un nombre, como el de una variable cualquiera. El nombre $this es un comodín que se emplea dentro del ámbito de la declaración de la clase para referirse al objeto en uso en un momento dado, sea cual sea el nombre de ese objeto. Así, el método constructor asigna los argumentos recibidos a las propiedades del objeto que está construyendo.

INSTANCIANDO LA CLASE

Hasta ahora, lo que hemos comentado de la clase ha sido un poco arcano. Ahora vamos a concretizarlo y aclarar conceptos viendo cómo creamos una instancia de esa clase. Para ello, vamos a ver el archivo index.php del proyecto:

Veamos que ocurre cuando invocamos a http://localhost/primer_ejemplo. En primer lugar se incluye el archivo de la clase que vamos a usar (src/CrearTelevisorClass.php). Es necesario disponer del archivo de la clase en el script donde vamos a usarlo, evidentemente.

Como comentario al vuelo, permíteme decirte que el uso de include() no es la mejor forma de cargar la definición de la clase. No voy a decirte ahora cual sería la mejor manera, ni por qué no es aconsejable hacerlo como lo estamos haciendo aquí. El uso de include(), en este caso, es algo cómodo, por su fácil comprensibilidad y, dado que lo que ahora nos interesa es ver los conceptos básicos de POO, de momento nos vale. En posteriores artículos te hablaré de la forma correcta de cargar la definición de una clase en un script.

Lo siguiente que hacemos es crear un objeto, llamado $miTele, usando el operador new y el nombre de la clase de la que vamos a instanciar el objeto. Cuando se crea un objeto, el nombre de la clase debe ir SIEMPRE seguido de paréntesis argumentales. En este caso, se crea un objeto de una clase que requiere tres argumentos. Si no se requirieran argumentos, los paréntesis estarían vacíos, pero DEBEN estar ahí.

Con esta instrucción, se ha invocado el método constructor de la clase. Tú no lo has invocado con ninguna instrucción específica (no has llamado a ningún método __construct()). Es algo que ocurre de forma transparente al crear una instancia de una clase. El intérprete de PHP reconoce que el nombre __construct() declara el constructor, y que debe ejecutarse cada vez que se instancia la clase.

En PHP existen algunos métodos que se conocen, genéricamente, con el nombre de métodos mágicos. Se distinguen porque todos se inician con un doble guion bajo. Aunque en estos artículos iremos conociendo los más interesantes, puedes leer más al respecto en http://php.net/manual/es/language.oop5.magic.php.

Observa que, en la creación del objeto, pasamos tres argumentos, que son recibidos, en el mismo orden, por el constructor de la clase. Veamos que ocurre con ellos. Lo primero que debes saber es que el objeto $miTele, por el hecho de pertenecer a la clase CrearTelevisorClass, tiene tres propiedades, que son las que se declaran al principio de la clase. Sin embargo, si no hacemos «algo», esas propiedades no tienen ningún valor, ya que así las hemos declarado (de hecho, se crean con el valor NULL). Sin embargo, el constructor coge los tres argumentos y los asigna a las propiedades. Y aquí es donde entra en juego el comodín $this. Cuando creamos el objeto $miTele y se ejecuta el constructor, $this se refiere, precisamente, a dicho objeto. Por lo tanto, cojamos la siguiente instrucción del constructor:

$this->color = $color;

Esta instrucción coge el valor que ha entrado por el argumento $color (negro, en este ejemplo), y lo asigna a la propiedad color del objeto en curso que, en este caso, es $miTele. En este ejemplo, esto equivaldría a haber escrito:

$miTele->color = $color;

Sin embargo, al usar $this, esto nos sirve para cualquier objeto que vayamos a construir. Si hubiéramos puesto la instrucción como en la última línea que vemos, ya no nos valdría para crear un objeto con otro nombre. Usando $this, en cambio, esto actúa, siempre, sobre el objeto con el que estamos trabajando, se llame como se llame.

El operador -> permite referenciar una propiedad o un método de un objeto determinado. Si vienes de otros lenguajes, como Java, Python o C++ estarás acostumbrado a usar un punto (.). En PHP el punto se usar como operador de concatenación de cadenas, por lo que, para referenciar propiedades o métodos de objetos era necesario otro operador.

EL OBJETO CREADO

Una vez creado el objeto, vamos a visualizarlo, para comprobar que el constructor ha hecho su trabajo. Para ello, recurrimos a:

var_dump ($miTele);

Lo que obtenemos, en la pantalla del navegador, es:

object(CrearTelevisorClass)#1 (3) {
  ["color":"CrearTelevisorClass":private]=>
  string(5) "negro"
  ["pulgadas":"CrearTelevisorClass":private]=>
  string(2) "42"
  ["pantalla":"CrearTelevisorClass":private]=>
  string(4) "OLED"
}

Veamos que tenemos aquí. La primera línea nos informa de que se trata de un objeto (object) y, entre paréntesis, nos indica el nombre de la clase a la que pertenece. También nos muestra un numeral (#1) que usa PHP internamente, y que nos dice que es el objeto número 1 de los que hay declarados. Como sólo hay declarado un objeto, es lógico que tenga el número 1. A continuación nos aparece, entre paréntesis, el número 3. Esto se refiere al número de propiedades que tiene declaradas el objeto.

Después encontramos las referencias a las propiedades del objeto. Para cada una de ellas, entre corchetes, aparece el nombre de la propiedad, el nombre de la clase a la que pertenece, y la cláusula private, que es como hemos declarado la propiedad (enseguida comentamos sobre este punto). Estos tres datos aparecen separados por el operador : y, en conjunto, constituyen el índice de cada propiedad (observa que, el formato global de datos es el de una matriz asociativa de PHP). Debajo de la clave que representa cada propiedad, aparece su valor.

ENCAPSULACIÓN

Aquí viene la cuestión de haber declarado las propiedades de la clase como privadas. Supongamos que, en el script index.php, añadimos (digamos, por ejemplo, al final del mismo), la siguiente instrucción:

var_dump($miTele->color);

Se supone que pretendemos leer el valor de la propiedad $color del objeto que hemos instanciado. Cuando se intenta ejecutar el programa, lo que obtenemos en la pantalla del navegador, al llegar a esta instrucción, es lo siguiente:

Fatal error: Uncaught Error: Cannot access private property CrearTelevisorClass::$color in C:\xampp\htdocs\p1\index.php:15
Stack trace:
#0 {main}
  thrown in C:\xampp\htdocs\p1\index.php on line 15

Esto se debe a que, al ser una propiedad privada, no podemos leerla desde fuera de la clase. Ahora imagina que tratamos de escribirla, para cambiar su valor, por ejemplo, así:

$miTele->color = "rojo";

El resultado es el mismo: un mensaje de error que nos dice que no se puede acceder a esa propiedad, por ser privada. En esto consiste la encapsulación: en declarar las propiedades de ámbito privado, para evitar que puedan ser leídas o sobrescritas desde fuera del código accidentalmente.

GETTERS Y SETTERS

Lo de la encapsulación está muy bien. Al declarar las propiedades de ámbito privado evitamos que se lean o sobrescriban accidentalmente. Sin embargo, surge la cuestión de la rigidez: resulta que, una vez creado el objeto, no podemos leer una propiedad individualmente, sino todo el objeto del tirón, como hemos visto en el ejemplo. Y tampoco podemos modificar los valores si fuera necesario. Esto convierte nuestro objeto en una estructura rígida, inamovible e inaccesible lo que, en la práctica, y la hace buena para nada.

Por supuesto, todo tiene solución. Las propiedades, al ser de ámbito privado, no son accesibles desde fuera de la clase… pero sí lo son desde dentro de la misma. Lo que tenemos que hacer es añadirle a la definición de la clase unos métodos (llamados, genéricamente, getters), que permitan recuperar el valor de cada una de las propiedades. También le añadiremos unos métodos (llamados, genéricamente, setters) que permitirán modificar las propiedades. Estos métodos sí serán declarados como públicos, por lo que sí se podrá acceder a ellos desde fuera de la clase.

Lo que hemos hecho es una copia de primer_ejemplo en segundo_ejemplo, donde hemos modificado la clase CrearTelevisorClass.php, que ahora queda así:

Ahora vamos a ver cómo emplear estos métodos para poder sobrescribir el valor de una propiedad, o leerlo, desde fuera de la clase. Para ello nos vamos al listado index.php de segundo_ejemplo:

Si pruebas este código verás como ahora sí te es posible, usando los métodos al efecto, cambiar el valor de las propiedades, o leerlas, estando en un script que está operando desde «fuera» de la clase.

Y si con estos métodos se pueden sobrescribir las propiedades de un objeto, o leerse ¿para qué usar la encapsulación? ¿Por que no declarar, directamente, las propiedades como públicas (usando public en lugar de private)? Bueno. Antes de responder, déjame hacerte un comentario. El modificador public es el que se usa por defecto. Por lo tanto, ponerlo o no es irrelevante. A la hora de declarar una variable en una clase es lo mismo escribir public $propiedad = ""; que $propiedad = ""; La cuestión de usar el modificador es semántica en el caso de public, para que el código quede más claro. Y, respecto a la pregunta (que, sin duda, te estabas haciendo), declarar las variables como private y usar setters y getters nos da una mayor estructuración sobre el código. Generamos un código mucho más limpio, y necesitamos emplear procesos tipificados para acceder a las propiedades. En el fondo, podemos considerarlo trivial, pero no lo es. Forma parte de las buenas prácticas de programación, desde el momento en que nos fuerza a hacer las cosas de una forma mucho más clara. Al usar setters y getters en el código que se usa fuera de la clase, sobre todo en códigos largos y complejos, tenemos una visión de conjunto más clara de lo que se hace en cada momento.

CONCLUYENDO

En este artículo hemos sentado, de manera práctica, conceptos fundamentales de la OOP. En el siguiente, seguiremos profundizando en otros conceptos de este paradigma de programación, que necesitamos dominar si queremos llegar a ser buenos desarrolladores. Como es habitual, los códigos de ejemplo de este artículo puedes descargarlos en este enlace.

   

Deja un comentario