Quand utiliser le pattern Value Object ?

Cet article a été publié depuis plus de 6 mois, cela signifie que le contenu peut ne plus être d'actualité.

Il y a quelques mois, je parlais du pattern Objets-Valeur (ou Value Object en anglais). Dans le billet présentant brièvement ce patron de conception, j'évoquais le fait qu'il est souvent méconnu des développeurs PHP. De ce fait les exemples sont assez rares et il est alors difficile de savoir quand et/ou comment l'utiliser.

Lorsque l'on évoque ce pattern, le principal exemple qui est cité est celui de l'objet Money permettant de gérer la notion d'argent (valeur + devise). Mais il existe une multitude de projet ne faisait pas appel à cette notion et de ce fait le concept reste abstrait.

Il peut être pourtant relativement simple de détecter à quel moment nous pouvons et/ou devons utiliser un Objet-Valeurs. La principale structure de données utilisées par les développeurs PHP est le tableau associatif. Dans bien de cas, ce dernier peut être remplacé par un objet de type Objet-Valeurs.

Par exemple, prenons le code suivant qui parse un fichier XML :

public function parseLine($data)
{
    // ... code permettant de traiter les données

    return [
        'name'        => trim($columns[0]->textContent),
        'frequency'   => trim($columns[1]->textContent),
        'location'    => trim($columns[2]->textContent),
        'transmitter' => trim($columns[3]->textContent),
    ];
}

Dans cet exemple, la fonction renvoie un tableau associatif contenant les informations d'une donnée lue dans un fichier XML. On pourrait dans ce cas utiliser un Value-Objet. Cela aurait de multiples avantages :

  • Avoir un objet où il serait possible d'avoir les données caractéristiques de l'élément (les propriétés) ainsi qu'un ensemble de comportement que nous pouvons y appliquer (les méthodes).
  • Cela permet de rendre le code plus lisible et compréhensible par les autres développeurs. Pas besoin d'analyser le contenu de la variable puisque cette dernière est décrite au travers d'un objet.

On pourrait ainsi réécrire le code sous la forme suivante :

public function parseLine($data)
{
    // ... code permettant de traiter les données

    return new RadioFrequency(
        trim($columns[0]->textContent),
        trim($columns[1]->textContent),
        trim($columns[2]->textContent),
        trim($columns[3]->textContent)
    );
}

Notons que si nous souhaitons faire les choses correctement, il serait nécessaire de déléguer la création de notre objet RadioFrequency à une fabrique (Factory). Bien que cela peut sembler compléxifier notre code en rajoutant une classe supplémentaire, c'est en réalité tout le contraire. Au travers de cette modification, nous respectons le principe de responsabilité unique.

Ainsi, le code final serait :

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

public function parseLine($data)
{
    // ... code permettant de traiter les données

    return $this->radioFrequencyFactory->createFromDOMNode($columns);
}

Au final, n'est-il pas plus agréable de manipuler un objet clairement identifié et ayant un comportement défini plutôt qu'un tableau où il est difficile de connaître son contenu et les actions qui peuvent y être appliquées sans effectuer une inspection xDebug ou sans dumper ce dernier ?