Objetos inmutables

Los objetos inmutables son objetos que se instancian con unos valores que no cambian a lo largo de su ciclo de uso.

DTO: Data Transfer Objects

Se trata de objetos simples que se utilizan para mover datos entre procesos agrupando varias llamadas en una sola (Fowler). Otra definición los considera como contenedores ligeros y serializables para distribuir datos desde una capa de servicio.

Podrías considerarlos como el equivalente de una interfaz pero para datos. Suponen un contrato entre las partes acerca de la forma en que comparten información.

Los DTO no suelen tener comportamiento, aparte del necesario para inicialización y para serialización. En cualquier caso no lleva lógica de negocio.

En su versión más simple un DTO es un objeto con propiedades públicas y un constructor para instanciarlo y asignar los valores. Esto no es muy diferente de lo que podríamos construir con un array asociativo y es frecuente encontrar opiniones a favor de los arrays basadas en su sencillez y velocidad. Sin embargo, la gran ventaja de los DTO viene de la mano del Type Hinting, lo cual nos permite usar los DTO como auténticos contratos sobre la transferencia de datos entre clases o partes de una aplicación, sumado a otros muchos beneficios derivados del uso de objetos.

Por ejemplo, el siguiente bug puede pasar fácilmente desapercibido, asumiendo que el array tiene una clave ‘name’:

1 $name = $data['nmae'];

Mientras que el código equivalente con un DTO simple nunca funcionaría:

1 $name = data->nmae;

Sin embargo, el hecho de un objeto tenga propiedades que son accesibles públicamente tiene el riesgo de que podrían modificarse, de modo que una forma más explícita sería hacer privadas las propiedades del DTO y añadirle getters para recuperar cada una de ellas. Es un poco más engorroso pero de esta forma nos aseguramos de que los objetos son realmente inmutables y los valores se mantienen sea cual sea el proceso por el que tengan que pasar.

Cuando usar DTO

Patrón objeto-parámetro

Cuando un método o función necesita muchos argumentos es posible simplificar su signatura agrupando esos argumentos en uno o más objetos simples, que bien pueden ser DTO.

Observa el siguiente código:

 1 <?php
 2 
 3 class DBConnector {
 4 
 5 	private $host;
 6 	private $port;
 7 	private $user;
 8 	private $password;
 9 	private $database;
10 	
11 	public function connect($host, $port, $user, $password, $database = null)
12 	{
13 		// Validation stuff
14 		$this->host = $host;
15 		$this->port = $port;
16 		$this->user = $user;
17 		$this->password = $password;
18 		$this->database = $database;
19 		
20 		// Connection stuff
21 	}
22 }
23 
24 ?>

En este ejemplo, la clase DBConnector tiene que llevar la cuenta de los detalles de la conexión, lo que lleva como consecuencia que, entre otras cosas, debe ocuparse de validarlos. Pero esa no debería ser su tarea (Principio de Responsabilidad Única), sino que los datos de conexión deberían venir validados, pero: ¿dónde y cuándo sucedería esa validación?

Ahora compara con este otro código:

 1 <?php
 2 
 3 class DBSettings {
 4 
 5 	private $host;
 6 	private $port;
 7 	private $user;
 8 	private $password;
 9 	private $database;
10 	
11 	public function __construct($host, $port, $user, $password, $database = null)
12 	{
13 		// Validation stuff
14 		$this->host = $host;
15 		$this->port = $port;
16 		$this->user = $user;
17 		$this->password = $password;
18 		$this->database = $database;
19 	}
20 	
21 	public function getHost()
22 	{
23 		return $this->host;
24 	}
25 	
26 	public function getPort()
27 	{
28 		return $this->port;
29 	}
30 	
31 	public function getUser()
32 	{
33 		return $this->user;
34 	}
35 	
36 	public function getPassword()
37 	{
38 		return $this->password;
39 	}
40 	
41 	public function getDatabase()
42 	{
43 		return $this->database;
44 	}
45 	
46 }
47 
48 class DBConnector
49 {
50 	private $settings;
51 	
52 	public function connect(DBSettings $settings)
53 	{
54 		$this->settings = $settings;
55 		// Connection stuff
56 	}
57 }
58 
59 ?>

En este ejemplo usamos un DTO para contener los datos de conexión y hacemos que el DTO contenga el código para validarlos (aunque no lo hemos escrito en el ejemplo). Gracias a eso, siempre que inicializamos un objeto de tipo DBSettings será válido, por lo que DBConnector puede aceptarlo sin tener que hacer nada más.

Devolución de datos múltiples de un método

PHP, como otros lenguajes, sólo permite un único valor de vuelta de un método. Si necesitamos devolver más, podemos componer un DTO. Volvemos a lo mismo: podría hacerse con un array asociativo, pero el DTO nos permite forzar restricciones que se controlan por el propio intérprete de PHP y lanzan errores o excepciones.

Value Objects

El concepto viene del DDD y se refiere a objetos que representan valores importantes para el dominio, su igualdad viene dada por la igualdad de sus propiedades.

Los Value Objects tienen comportamientos. No tienen identidad ni un ciclo de vida.

El ejemplo clásico de Value Object es el dinero (Money), que usamos en precios, salarios, etc. Habitualmente modelamos los valores del dinero con números de coma flotante, pero el valor no es suficiente. El dinero es el valor y la moneda que se esté utilizando: no es lo mismo 10 dólares que 10 euros. Puede argumentarse que en muchas aplicaciones no es necesario tener en cuenta la moneda, pero si algún día en el futuro eso cambiase, el impacto en el código podría ser enorme.

Dado que en OOP pretendemos encapsular lo que cambia junto, tiene sentido crear una clase de objetos para representar dinero que tenga dos propiedades: valor y monedo.

 1 <?php
 2 
 3 class Money {
 4 	private $amout;
 5 	private $currency;
 6 
 7 	public function __construct($amount, $currency) {
 8 		$this->amount = $amount;
 9 		$this->currency = $currency;
10 	}
11 
12 	public function getAmount() {
13 		return $this->amount;
14 	}
15 }
16 ?>

Aunque un objeto sea inmutable, puede tener métodos que se basen en sus propiedades para generar nuevos valores. Dicho de otra forma, deben devolver instancias de nuevos objetos con los nuevos valores, en lugar de reasignar las propiedades del objeto valor.

 1 <?php
 2 
 3 class Money {
 4 	private $amout;
 5 	private $currency;
 6 	
 7 	public function __construct($amount, $currency) {
 8 		$this->amount = $amount;
 9 		$this->currency = $currency;
10 	}
11 	
12 	public function getAmount() {
13 		return $this->amount;
14 	}
15 	
16 	public function incrementByPercentage($pct) {
17 		return new Money($this->amount*(1+$pct), $this->currency);
18 	}
19 }
20 
21 $price = new Money(100, 'EUR');
22 $newPrice = $price->incrementByPercentage(.10);
23 echo $newPrice->getAmount();
24 
25 ?>

Usamos Value Objects cuando necesitamos representar valores:

  • No se pueden representar con un tipo básico del sistema.
  • Son complejos y agrupan varios datos de tipo simple que van junos.
  • Requieren una validación específica.

Por ejemplo, el dinero (y con él, precios, salarios, etc) puede representarse con un valor de coma flotante, pero en cuanto necesitamos gestionar la moneda se introduce un segundo dato. Encapsulamos ambos para manejarlos de manera conjunta.

Otro ejemplo típico es una dirección postal, se trata de varios datos string que van juntos para componer una dirección. Encapsulados en un Value Object son más fáciles de manejar.

Un ejemplo más sutil es el email. Una dirección de email puede representarse con un string, pero encapsulándolo en un Value Object podemos introducir las reglas de validación (email bien formado) en el constructor, asegurándonos de que todos los objetos email que manejemos sean válidos. Eso quizá no nos asegura que los emails sean reales, pero sí nos garantiza que están bien formados.

 1 <?php
 2 
 3 
 4 class Email {
 5 	private $email;
 6 	
 7 	public function __construct($email)
 8 	{
 9 		if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
10 			throw new InvalidArgumentException("$email is not a valid email");
11 		}
12 		$this->email = $email;
13 	}
14 	
15 	public function getValue()
16 	{
17 		return $this->email;
18 	}
19 }
20 
21 $emailsToTry = array(
22 	'franiglesias@mac.com',
23 	'franiglesias@',
24 	'@example.com',
25 	'user@example'
26 );
27 
28 foreach ($emailsToTry as $email) {
29 	try {
30 		$emailObject = new Email($email);
31 	} catch (Exception $e) {
32 		echo $e->getMessage().chr(10);
33 	}
34 
35 }
36 
37 ?>

http://culttt.com/2014/04/30/difference-entities-value-objects/