Blog

Los Value Objects (VO) son una pieza fundamental del diseño dirigido por dominio (Domain-Driven-Design o DDD) y de la programación orientada a objetos. Describen un concepto de nuestra capa de dominio.

Martin Fowler los define como:

A small simple object, like money or a date range, whose equality isn't based on identity.

En español:

Un objeto pequeño simple, como el dinero o una fecha, cuya igualdad no se basa en la identidad.

Las principales características de un VO son:

  • Describe un concepto.
  • Se valida en la construcción.
  • Se compara por valor, no por identidad.
  • Es inmutable.

Describe un concepto

Un VO describe un concepto de nuestra capa de dominio ya sea cuantificable o no, por ejemplo, un punto de un eje de coordenadas, la temperatura o la dirección de un cliente.

Validación en la construcción

Todo VO tiene que poder auto validarse durante la construcción, esto nos asegura que una vez construido el objecto es válido. Como buena práctica un VO solo debería esta compuesto por valores primitivos y otros VO. Por ejemplo, en un punto de un eje de coordenadas, los valores de X e Y solo pueden ser valores de tipo primitivo float, no tiene sentido que X o Y sean de tipo string o booleano.

final class Point
{
    private $x;

    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }
}

Identidad vs valor

¿Que significa que un VO se compara por valor, no por identidad? Bien, en los VO, tal y como indica su nombre, lo que importa es su valor. Veamos un pequeño ejemplo:

final class Point
{
    private $x;

    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX(): float
    {
        return $this->x;
    }

    public function getY(): float
    {
        return $this->y;
    }
}

$p1 = new Point(2, 2);
$p2 = new Point(2, 2);

En el ejemplo de arriba tenemos dos variables $p1 y $p2, ambas instancias de Point y ambas con las mismas coordenadas X e Y. A primera vista podemos decir que técnicamente los objetos son iguales, vamos a compararlos:

var_dump($d1 == $d2);
bool(true)

El resultado es true, todo correcto, ambos objetos tienen el mismo valor así que son iguales pero, ¿qué pasa si ahora usamos === para realizar la comparación?

var_dump($d1 === $d2);
bool(false)

Esta vez el resultado es false ya que la identidad de estos objetos no está basada en su valor, sino que viene definida por un identificador o referencia única que hace que estos objetos sean diferentes.

En PHP, el operador == se usa para evaluar la igualdad, mientras que el === para evaluar la identidad. Para facilitar la evaluación de dos VO podemos añadir un método isEqual() a el API de la clase Point:

final class Point
{
    private $x;

    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX(): float
    {
        return $this->x;
    }

    public function getY(): float
    {
        return $this->y;
    }

    public function isEqual(Point $point): bool
    {
        return ($this->x == $point->getX() && $this->y == $point->getY());
    }
}

$p1 = new Point(2, 2);
$p2 = new Point(2, 2);
$p3 = new Point(2, 4);

var_dump($p1->isEqual($p2));
bool(true);

var_dump($p1->isEqual($p3));
bool(false);

Inmutabilidad

Otro aspecto fundamental de los VO es que son inmutables, esto quiere decir que una vez definidos no pueden ser alterados. Ya que los VO están pensados para que sean pequeños y ligeros, cada vez que un VO tiene que ser alterado, se crea otro con los nuevos valores en su lugar.

Por ejemplo, si quisiéramos cambiar el valor X del punto $p1 definiríamos un setter que nos permitiese cambiar ese valor. Esto no esta permitido en VO ya que son inmutables, en su lugar, podemos hacer que el setter devuelva una instancia nueva de la clase con el nuevo valor:

final class Point
{
    private $x;

    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX(): float
    {
        return $this->x;
    }

    public function getY(): float
    {
        return $this->y;aa
    }

    public function setX(float $x): Point
    {
        return new self($x, $this->y);
    }

    public function setY(float $y): Point
    {
        return new self($this->x, $y);
    }

    public function isEqual(Point $point): bool
    {
        return ($this->x == $point->getX() && $this->y == $point->getY());
    }
}

$p1 = new Point(2, 2);
$p1 = $p1->setX(4);

Beneficios

Sabiendo las características de los VO podemos decir que nos ofrecen los siguientes beneficios:

  • Facilita la reusabilidad debido a la encapsulación de la lógica de dominio.
  • Evitamos validaciones repetitivas a lo largo de nuestra aplicación, el VO se encarga de auto validarse.
  • Al ser inmutable evitamos las alteraciones no deseadas a lo largo del ciclo de vida.

Libros Relacionados

Siguiente Postt Post Anterior