Non, vous ne faites pas de la programmation objet

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

Si vous posez la question à n’importe quel développeur, il vous répondra très certainement que oui, il fait bien de la programmation orientée objet. Effectivement, les langages de développement les plus utilisés actuellement tels que Python, C++, Java, C#, JavaScript, PHP ou encore Ruby permettent d’écrire des objets et de nombreux frameworks utilisent ces notions pour fonctionner. Est-ce pour autant que l’on fait réellement de la programmation orientée objet ?

Pour moi la réponse est clairement non. Ce n’est pas parce que l’on écrit des classes et que l’on peut les instancier, que l’on fait de la programmation orientée objet. Le fondement de ce paradigme, c’est l’encapsulation. Il s’agit du principe qui fait qu’un objet n’est pas une simple structure de données. Un objet c’est l’association de données (les propriétés d’une classe) et des comportements (les méthodes) applicables à ce dernier.

Pourtant, la majorité des frameworks et des ORM (Object-Relational Mapping) nous ont habitués à avoir et à créer des objets dénués de sens. On se contente bien souvent de classes qui contiennent des propriétés que l’on définit et récupère unitairement sans sémantique particulière comme dans l’exemple suivant:

class User
{
    private string $username;
    private string $passwordHash;
    /** @var string[] $bans */
    private array $bans;

    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): void
    {
        $this->username = $username;
    }

    public function getPasswordHash(): string
    {
        return $this->passwordHash;
    }

    public function setPasswordHash(string $passwordHash): void
    {
        $this->passwordHash = $passwordHash;
    }

    /**
     * @return string[]
     */
    public function getBans(): array
    {
        return $this->bans;
    }

    public function addBan(Ban $ban): void
    {
        $this->bans[] = $ban;
    }
}

Des objets avec uniquement des getters et des setters. Même le constructeur semble oublié. C’est ce que l’on appelle des modèles anémiques.

C’est pour moi le point le plus négatif des développeurs à l’heure actuelle, car au delà de ne pas respecter le paradigme initial, cela entraine également une perte de sens dans le code de nos projets. Et non, modéliser nos objets ainsi ça ne vient pas du DDD (Domain Driven Design) comme j’ai pu l’entendre. Faire des objets qui mutent et qui ont des modèles riches, c’est le principe de base de la programmation objet. Le DDD ne fait que s’appuyer sur ces fondements.

Lorsque vous écrivez vos objets, pensez comportement. Écrivez des objets riches, appuyez-vous sur des mutations, utilisez des DTO (Data Transfer Object) si nécessaire. L’utilisation de getters et setters devrait être une exception. Implémentez des méthodes qui représentent le comportement de vos objets.

Si l’on reprend l’exemple précédent, ce dernier pourrait être modifié ainsi:

class User
{
    public function __construct(
        private string $username,
        private string $passwordHash,
        private array $bans = [],
    ) {
    }

    public function toNickname(): string
    {
        return $this->username;
    }

    public function authenticate(string $password, callable $checkHash): bool
    {
        return $checkHash($password, $this->passwordHash) && ! $this->hasActiveBans();
    }

    public function changePassword(string $password, callable $hash): void
    {
        $this->passwordHash = $hash($password);
    }

    public function ban(\DateInterval $duration): void
    {
        assert($duration->invert !== 1);

        $this->bans[] = new Ban($this);
    }
}

Cet exemple, aussi court puisse-t-il être, condense de nombreux concepts:

  • Un constructeur pour initialiser les propriétés obligatoires,
  • Des accesseurs métier qui permettent d’accéder uniquement aux informations utiles en dehors de l’objet,
  • Des mutations qui permettent de modifier l’état de notre objet en s’assurant que toutes les propriétés liées à un comportement soient spécifiées.

Et oui, cela fonctionne avec votre framework préféré même si ce dernier ne l’a pas décrit tel quelle dans sa documentation, préférant opter pour une approche RAD (Rapid Application Development) pour une prise en main simplifiée et des résultats plus rapides.