¿Por qué no hacer todo public? Encapsulación

Vídeo


En las entregas anteriores, al definir un método o una propiedad, siempre los hemos declarado public. Ya comenté que también se pueden definir como private o protected.

¿Por qué tenemos estas tres opciones? ¿Cuál es la diferencia?

Cuando una propiedad es public podemos acceder a ella desde fuera de la clase. Lo hemos visto ya varias veces:

$noticia->titulo = 'Hola mundo';

Pero cuando la propiedad (o el método) es private ya no podemos acceder a ella desde fuera de la clase. Vamos a cambiar la propiedad título para hacerla private y ver qué pasa:

private $titulo;

Con lo que el código quedaría así:

class Noticia {
    private $titulo;

    public function mostrar()
    {
        echo $this->titulo . "\n";
    }

    public function cambiarTitulo($titulo)
    {
        $this->titulo = $titulo;
    }

}

$noticia = new Noticia();
$noticia->titulo = "Noticia cambiada";
$noticia->mostrar();

Si ejecutamos el programa tendremos un error:

PHP Fatal error:  Cannot access private property Noticia::$titulo

Esto se debe a que ahora la propiedad título ahora es privada, sólo se puede acceder a ella desde métodos que estén dentro de la clase. Ya no existe para el mundo exterior. Ya no podemos hacer:

$noticia->titulo = "Noticia cambiada";

¿Y cómo la modificamos si no podemos acceder a ella desde el exterior? Pues para eso tenemos el método cambiarTitulo() del capítulo anterior:

$noticia->cambiarTitulo("Noticia cambiada");

Si lo ejecutamos vemos que no tenemos problema porque cambiarTitulo es un método public.

Si definiésemos cambiarTitulo() como método privado:

private cambiarTitulo($titulo)

ya no podríamos acceder tampoco a él y veríamos un error similar:

PHP Fatal error:  Call to private method Noticia::cambiarTitulo()

También podemos definir una propiedad (o un método) como protected. A éstas propiedades y métodos solo se puede acceder desde la propia clase y desde clases heredadas (ya hablaremos de ésto en otro artículo).

Pues mejor hacer todo public ¿no?

Pero ¿esto no es una complicación innecesaria? ¿Qué sentido tiene? ¿No sería más cómodo hacer todo public?

Aparentemente sería mucho más cómodo, pero no es una buena práctica.

Piensa en la oficina de correos. Tú llegas, entregas una carta y la pagas. Después de un largo proceso el cartero entrega la carta en destino. Eso es todo lo que tú vas a ver del funcionamiento de Correos. La carta pasará por un proceso de clasificación por destino, se almacenará en un depósito temporal con cartas al mismo sitio, se meterán todas un camión, se descargarán, y se asignarán al cartero correspondiente. Tú no ves nada de ésto... y ni te importa.

Ahora imagina que pudieras circular libremente por la oficina de correos y dejaras tú mismo la carta en el depósito adecuado. Pero lo que no sabes es que al meter la carta en el depósito hay que escanearla y tú no lo haces. Puede que por eso la carta se pierda. Sería un desastre.

Con las clases pasa igual. No se puede permitir que cualquiera utilice la clase que has creado de cualquier manera porque podría dar problemas.

Imagina que quieres que las noticias tengan un título con una longitud máxima de 30 caracteres. Tendrías que modificar la función cambiarTitulo() para que lo controle:

class Noticia {
    private $titulo;

    public function mostrar()
    {
        echo $this->titulo . "\n";
    }

    public function cambiarTitulo($titulo)
    {
        if(strlen($titulo)<30)
        {
            echo "Error: título es demasiado corto.\n";
            return false;
        }
        $this->titulo = $titulo;
    }
}


$noticia = new Noticia();
$noticia->cambiarTitulo("Noticia cambiada");
$noticia->mostrar();

Si lo ejecutamos vemos el mensaje:

Error: título demasiado corto

¡Perfecto!

Ahora pasa un mes, vuelves a usar tu clase noticia en otro proyecto (o la usa un compañero tuyo). Es posible que se te olvide que existe ese método y cambies la propiedad título directamente:

$noticia->titulo = "Noticia cambiada";

Si ejecutas el programa verás que no hay ningún problema. El programa acepta el título aunque sea demasiado corto:

Noticia cambiada

¿Ves ahora la diferencia?

En cambio si definimos la propiedad título como private:

private $titulo;

ya no se podrá acceder a ella directemente y obligaremos al que use la clase (y a nosotros mismos) a usar la función cambiarTitulo.

Cuando tenemos un método para modificar el valor de una propiedad se la suele llamar setter (del inglés 'set'). Seguramente ya habrás oído el término setter. Çomo puedes ver es un método normal y corriente al que alguien ha decidido llamarle así.

Normalmente, a este tipo de métodos se les define añadiendo 'set' al principio:

public setTitulo()

No tiene mayor importancia ni es obligatorio hacerlo así. Es cuestión de gustos, aunque seguir las convenciones ayuda cuando en tu proyecto trabaja más gente. Lo mejor es ponerse de acuerdo al empezar un nuevo proyecto y usar el sistema que se decida por mayoría (o que lo decidan los puños).

Puedes pensar que no necesitas un setter porque lo único que haces es asignar un valor a una propiedad. El problema es que esto puede cambiar en el futuro y hacer privada una propiedad que antes era pública puede causar problemas a los programas que usen tu clase.

Encapsulación

Vamos a volver al ejemplo de Correos. Imagina que correos cambia la máquina clasificadora de cartas, o inventan el teletransporte. Esto no te afecta para nada, tú sigues entregando la carta en la oficina y la carta llega a su destino (si no se pierde, claro).

Del mismo modo tú puedes modificar tus propiedades y métodos privados y el que usa la clase no va a notar ninguna diferencia porque solo tiene acceso a los métodos públicos. Cómo funcione la clase por dentro no le afectará.

Esto es lo que se llama encapsulación.

Es importante que pensemos bien qué partes de nuestra clase van a ser públicas porque esas no deberían cambiar nunca. Si lo hacemos, podemos hacer que todos los programas que usen nuestra clase dejen de funcionar.

Imagina que alguien usa tu clase y utiliza el método cambiarTitulo. Al de un tiempo sacas una nueva versión de tu clase pero cambias el nombre del método a setTitulo. El que use tu clase tendrá que modificar su programa para poder usar la nueva versión de tu clase.

Getters

No podemos acceder directamente a la propiedad titulo. Para eso usamos el método cambiarTitulo(). ¿Y si lo que queremos es leer el valor de esa propiedad?

Para esto usamos unos métodos, a los que a alguien se le ocurrió llamar getters, que nos devuelven el valor de la propiedad:

public function getTitulo()
{
        return $this->titulo;
}

Aquí, al igual que sucedía con los setters podemos seguir la convención de añadir get al principio del nombre o podemos usar lo que más nos guste.

Autor:
Nivel: Intermedio
Palabras clave:
Fecha publicado:
Fecha actualizado: 23-08-2016

Otros capítulos de la misma serie

Este capítulo es parte de la serie: Curso de Programación orientada a objetos en PHP.

Y muchos más en preparación.

Disponible en los planes: PHP a tope Laravel hero