Maison >développement back-end >tutoriel php >PHP Master | Le motif d'objet nulle - le polymorphisme dans les modèles de domaine

PHP Master | Le motif d'objet nulle - le polymorphisme dans les modèles de domaine

Christopher Nolan
Christopher Nolanoriginal
2025-02-25 14:53:08450parcourir

PHP Master | The Null Object Pattern - Polymorphism in Domain Models

Points de base

  • Le motif d'objet vide est un motif de conception qui utilise le polymorphisme pour réduire le code conditionnel, ce qui rend le code plus concis et plus facile à entretenir. Il fournit un objet non fonctionnel qui peut remplacer l'objet réel, éliminant ainsi le besoin de vérifications de valeur nulle.
  • Le mode objet vide peut être utilisé en conjonction avec d'autres modes de conception, tels que le mode d'usine créant et renvoyant des objets vides, ou le mode politique modifiant le comportement d'un objet à l'exécution.
  • L'inconvénient potentiel du motif d'objet vide est qu'il peut conduire à la création d'objets inutiles et augmenter l'utilisation de la mémoire. Il peut également rendre le code plus compliqué car des classes et des interfaces supplémentaires sont nécessaires.
  • L'implémentation du modèle d'objet vide nécessite la création d'une classe d'objets vide qui implémente la même interface que l'objet réel. Cet objet vide fournit une implémentation par défaut pour toutes les méthodes de l'interface, ce qui lui permet de remplacer l'objet réel. Cela rend le code plus robuste et moins sujet aux erreurs.

Bien qu'il ne se conforme pas pleinement aux spécifications, on peut dire que l'orthogonalité est l'essence d'un système logiciel basé sur le principe de "bonne conception", dans laquelle les modules sont découplés les uns des autres, ce qui rend le système moins sujet à des problèmes rigides et fragiles. Bien sûr, il est plus facile de parler des avantages des systèmes orthogonaux que de gérer réellement les systèmes de production; ce processus est généralement une poursuite de l'utopie. Même ainsi, il n'est en aucun cas un concept utopique pour atteindre des composants très découplés dans un système. Divers concepts de programmation tels que le polymorphisme permettent la conception de programmes flexibles, dont les pièces peuvent être commutées au moment de l'exécution, et leurs dépendances peuvent être exprimées sous des formes abstraites plutôt que des implémentations concrètes. Je dirais que l'ancienne devise de la «programmation axée sur l'interface» a été largement adoptée au fil du temps, que nous implémentions une infrastructure ou une logique d'application. Cependant, lorsque vous entrez dans le domaine des modèles de domaine, la situation est très différente. Franchement, c'est un scénario prévisible. Après tout, pourquoi un réseau d'objets interdépendants (qui ont des données et des comportements devraient-ils être limitées par des règles métier bien définies) être polymorphes? Cela n'a pas beaucoup de sens en soi. Cependant, il y a quelques exceptions à cette règle, qui peuvent éventuellement s'appliquer à cette situation. La première exception est l'utilisation d'un proxy virtuel, qui est effectivement la même interface que l'objet de domaine réel implémente. Une autre exception est le soi-disant «cas nul», qui est un cas spécial où une opération peut finir par attribuer ou renvoyer des valeurs nuls au lieu de remplir une entité bien remplie. Dans les approches traditionnelles non polymorphes, l'utilisateur du modèle doit vérifier ces valeurs nulles "nuisibles" et gérer gracieusement la condition, entraînant une explosion d'instructions conditionnelles tout au long du code. Heureusement, cette situation apparemment déroutante est facilement résolu en créant simplement une implémentation multi-branchage de l'objet de domaine qui implémentera la même interface que l'objet en question, sauf que sa méthode ne fait rien, donc le client sera le code est libéré de Vérifiez à plusieurs reprises les valeurs nulles laides lors de l'exécution d'une opération. Sans surprise, cette approche est un modèle de conception appelé objets vides qui apportent les avantages du polymorphisme à l'extrême. Dans cet article, je vais démontrer les avantages de ce modèle dans plusieurs cas et vous montrer comment ils sont étroitement attachés aux méthodes polymorphes.

Traiter les conditions non polymorphes

Comme on pourrait s'y attendre, il existe plusieurs façons d'essayer lors de la montre les avantages des modèles d'objets vides. Une approche particulièrement simple que j'ai trouvée consiste à implémenter un mappeur de données qui peut finir par renvoyer des valeurs nulles d'un chercheur à usage général. Supposons que nous ayons créé avec succès un modèle de domaine squelette qui se compose d'une seule entité utilisateur. L'interface et sa classe sont les suivantes:

<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}</code>
<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}</code>

La classe utilisateur est une structure réactive qui implémente certains mutateurs / accessoires pour définir certaines données et comportements utilisateur. Avec cette classe de domaine construite, nous pouvons maintenant aller plus loin et définir un mappeur de données de base qui isole notre modèle de domaine et notre couche d'accès aux données les uns des autres.

<code class="language-php"><?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

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

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }

    private function createUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}</code>

La première chose qui devrait apparaître est que la méthode FetchById () du mappeur est une coquine dans le bloc, car elle renvoie efficacement NULL lorsqu'aucun utilisateur dans la base de données correspond à l'ID donné. Pour des raisons évidentes, cette condition maladroite fait que le code client doit travailler dur pour vérifier la valeur nulle à chaque fois qu'il appelle le chercheur du mappeur.

<code class="language-php"><?php use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperUserMapper;

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");

$userMapper = new UserMapper($adapter);

$user = $userMapper->fetchById(1);

if ($user !== null) {
    echo $user->getName() . " " . $user->getEmail();
}</code>

À première vue, il n'y aura pas de problème, vérifiez-le simplement en un seul endroit. Mais si la même ligne apparaît dans plusieurs contrôleurs de pages ou couches de service, allez-vous tomber sur un mur de briques? Avant de vous en rendre compte, le null apparemment innocent rendu par le mappeur produira beaucoup de conditions répétitives, ce qui est un mauvais présage de mauvaise conception.

Supprimer les instructions de condition du code client

Cependant, il n'est pas nécessaire de s'inquiéter, car c'est exactement le cas où le modèle d'objet vide montre pourquoi le polymorphisme est une aubaine. Si nous voulons nous débarrasser de ces instructions conditionnelles ennuyeuses une fois pour toutes, nous pouvons implémenter la version polymorphe de la classe utilisateur précédente.

<code class="language-php"><?php namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() { }

    public function setName($name) { }
    public function getName() { }

    public function setEmail($email) { }
    public function getEmail() { }
}</code>

Si vous vous attendez à ce qu'une classe d'entité complète emballe toutes sortes de décorations, vous êtes probablement très déçu. La version "NULL" de l'entité est conforme à l'interface correspondante, mais la méthode est un wrapper vide et il n'y a pas d'implémentation réelle. Bien que l'existence de la classe Nulluser ne nous apporte évidemment rien qui mérite des éloges, c'est une structure concise qui nous permet de jeter toutes les déclarations conditionnelles précédentes dans la poubelle. Vous voulez voir comment il est mis en œuvre? Tout d'abord, nous devons faire du pré-travail et reconstruire le mappeur de données afin que son chercheur renvoie un objet utilisateur vide au lieu d'une valeur vide.

<code class="language-php"><?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser,
    ModelNullUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

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

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        return $this->createUser($this->adapter->fetch());
    }

    private function createUser($row) {
        if (!$row) {
            return new NullUser;
        }
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user; 
    }
}</code>
La méthode CreateUser () du Mapper masque une minuscule condition car elle est désormais responsable de la création d'un utilisateur vide lorsque l'ID transmis au Finder ne renvoie pas un utilisateur valide. Même ainsi, ce coût nuancé économise non seulement le travail du code client faisant de nombreuses vérifications répétées, mais le transforme également en un consommateur lâche qui ne se plaindra pas lorsqu'il doit faire face aux utilisateurs vides.

<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}</code>

Le principal inconvénient de cette approche polymorphe est que toute application qui l'utilise deviendra trop lâche car elle ne se bloque jamais lorsqu'elle traite des entités non valides. Dans le pire des cas, l'interface utilisateur ne montrera que des lignes vierges, mais rien de vraiment bruyant nous rend dégoûtés. Cela est particulièrement évident lors de la numérisation de l'implémentation actuelle de la classe nulsser précoce. Même s'il est possible, sans parler de celui recommandé, il peut également encapsuler la logique dans des objets vides tout en gardant son polymorphisme inchangé. Je peux même dire que les objets vides sont parfaits pour encapsuler des données et des comportements par défaut qui ne devraient être exposés au code client que dans quelques cas spéciaux. Si vous êtes assez ambitieux et que vous souhaitez essayer ce concept avec un simple objet utilisateur vide, la classe Nulluser actuelle peut être refactorisée comme suit:

<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}</code>

La version améliorée de Nulluser est légèrement plus expressive que son prédécesseur silencieux, car son Getter fournit quelques implémentations de base pour renvoyer certains messages par défaut lors de la demande d'un utilisateur non valide. Bien que Trivial, ce changement a un impact positif sur la façon dont le code client gère les utilisateurs vides, car les utilisateurs savent au moins que certains problèmes surviennent lorsqu'ils essaient d'extraire les utilisateurs inexistants du stockage. Il s'agit d'une bonne percée, non seulement montrant comment implémenter des objets vides qui ne sont pas du tout vides, mais aussi à quel point il est facile de déplacer la logique à l'intérieur d'objets pertinents en fonction de besoins spécifiques.

Conclusion

Certains pourraient dire que la mise en œuvre d'objets vides est gênante, en particulier en PHP, où les concepts de base de la POO (comme le polymorphisme) sont considérablement sous-estimés. Ils ont raison dans une certaine mesure. Néanmoins, l'adoption progressive des principes de programmation et des modèles de conception dignes de confiance, ainsi que le niveau de maturité que le modèle d'objet linguistique a atteint, est d'aller de l'avant et de commencer à utiliser certains des «produits de luxe» qui étaient considérés comme des notions complexes et irréalistes Il n'y a pas longtemps, fournit toutes les bases nécessaires. Le modèle d'objet NULL entre dans cette catégorie, mais son implémentation est si simple et élégante qu'il est difficile de ne pas le trouver attrayant lors de l'effacement des vérifications nulles en double dans le code client. images de Fotolia

(En raison des limitations de l'espace, la partie FAQ du texte d'origine est omise ici.)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn