El principio Tell Don’t Ask en PHP

Tengo un ejemplo a medias tell-dont-ask.php

El principio «Tell, Don’t Ask» en programación orientada a objetos consiste en evitar preguntar algo a la clase hija y luego decirle que haga algo en base a su respuesta. En lugar de preguntar y luego hacer lo que se recomienda es decir a la clase hija que haga algo. Si puede lo hará y si no puede no lo hará.

Pero esto que acabo de decir seguro que no se entiende nada así que vamos a ver un ejemplo.

Ejemplo de la versión «Ask»

Este ejemplo no usa el principio «Tell, don’t ask». Aquí tenemos un Glotón que se vuelve loco por las gominolas yse hace con un tarro de ellas:

class TarroGominolas
{
    public $numeroGominolas = 0;

    public function __construct(int $cantidad)
    {
        $this->numeroGominolas = $cantidad;
    }
}

class Gloton
{
    /** @var TarroGominolas */
    private $tarro;

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

    public function come($cantidad)
    {
        echo "Voy a comer $cantidad gominolas!\n";
        
        if($this->tarro->numeroGominolas>=$cantidad) {
            $this->tarro->numeroGominolas -= $cantidad;
            echo "Ñam, ñam!\n";
        }
        else {
            echo "Oooh! No hay suficientes!\n";
        }
    }
}

$tarro = new TarroGominolas(10);
$gloton = new Gloton($tarro);

$gloton->come(10);
$gloton->come(5);

En este caso preguntamos a la clase TarroGominolas a ver si tiene suficientes para el glotón (Preguntamos). Si tiene suficientes el glotón se las come (Realizamos la acción). Si no tiene suficientes entonces llorará.

Antes de seguir debo indicar que este ejemplo es bastante malo porque, por simplificar, he puesto la propiedad $numeroGominolas como public cosa que no suelo hacer. Hubiera sido mejor crear unos métodos isGominolasSuficientes() y sacaGominolas() por ejemplo.

Versión Tell, don’t ask

Una versión Tell, don’t ask podría ser:

class TarroGominolas
{
    public $numeroGominolas = 0;

    public function __construct(int $cantidad)
    {
        $this->numeroGominolas = $cantidad;
    }

    public function sacaDelBote($cantidad)
    {
        if($this->numeroGominolas<$cantidad) {
            return false;
        }
        
        $this->numeroGominolas -= $cantidad;
        return $this->numeroGominolas;
    }
}

class Gloton
{
    /** @var TarroGominolas */
    private $tarro;

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

    public function come($cantidad)
    {
        echo "Voy a comer $cantidad gominolas!\n";

        if($this->tarro->sacaDelBote($cantidad)!==false) {
            echo "Ñam, ñam!\n";
        }
        else {
            echo "Oooh! No hay suficientes!\n";
        }
    }
}

$tarro = new TarroGominolas(10);
$gloton = new Gloton($tarro);

$gloton->come(10);
$gloton->come(5);

En esta versión no preguntamos al bote a ver si tiene suficientes y si las hay las sacamos. En este caso le pedimos al bote las gominolas. Si no hay suficientes nos devolverá false.

Tiene la ventaja de que estamos llevando la lógica a la función sacaDelBote() y no tenemos que preocuparnos de hacer comprobaciones cada vez.

A Martin Fowler parece que no le gusta este principio. Puede que sus argumentos te convenzan pero cuando tenemos que hacer una llamada a un sistema externo (por ejemplo una llamada a un WebService) nos ahorrará una llamada. Si no usamos este principio tenemos que llamar una vez al WebService para ver si el recurso está disponible y una segunda llamada para modificarlo.

Me siento solo, dime algo...