5
votes

PHP: quelle est la raison pour laquelle certaines personnes "falsifient" des méthodes abstraites en lançant exclusivement une exception dans le corps?

Récemment, je vois souvent du code comme celui-ci (cet exemple est tiré de Symfony):

protected function execute(InputInterface $input, OutputInterface $output)
{
    throw new LogicException('You must override the execute() method in the concrete command class.');
}

Quel est l’avantage ici de simplement marquer la méthode comme abstract ?

Quel est l'avantage pour le ...

  1. ... auteur de la bibliothèque?
  2. ... utilisateur de la bibliothèque?

J'ai déjà trouvé une question similaire pour Java ( Faux résumé méthodes utilisant des exceptions en Java ), mais cela n'a pas été très utile car ses réponses sont des suppositions et des opinions explicites.


1 commentaires

Quelques idées du génie logiciel (C #) . Se résume à "ne devrait jamais entrer en production" mais sert de drapeau rouge pendant le développement que quelque chose manque.


3 Réponses :


0
votes

seules les classes abstraites peuvent avoir des méthodes abstraites.

Vous ne pouvez pas créer d'objet à partir de classes abstraites.

Toutes les méthodes abstraites doivent être implémentées dans des classes non abstraites.

Exemple construit:

Imaginez ce qui suit:

abstract class Repository {
    public abstract function read();
    public abstract function write($object);
    public abstract function delete($object);
    public function connection() {
        //this is implemented for you by the framework so you don't have to do it every time
    }

Vous voulez maintenant implémenter un référentiel pour la journalisation des actions utilisateur (connecté, déconnecté, ... )

Vous ne voulez pas qu'aucune de ces entrées soit supprimée et c'est à ce moment que vous ne voulez pas implémenter la fonction delete . Mais vous devez le faire, car votre UserActionRepository n'est pas abstrait, car vous en avez réellement besoin. c'est là que vous lancez une exception.


20 commentaires

Pourquoi devrais-je instancier une classe qui n'a pas d'implémentation? C'est comme s'il était possible d'instancier une interface.


Lorsque vous n'avez pas besoin d'une implémentation, car vous pouvez être sûr que vous n'avez pas besoin de la méthode. Je vais essayer de trouver un exemple utile / réel


Merci, j'aimerais vraiment voir un exemple convaincant. Parce qu'à l'argument sans exemple, je répondrais "si vous n'avez pas besoin d'une méthode, ne la déclarez pas". Ou "si vous ne voulez pas que la classe soit étendue, marquez-la comme définitive". Ou "si vous devez être étendu et avoir une méthode implémentée, marquez-le comme abstrait". La situation "classe peut éventuellement être étendue, mais si elle est étendue, elle doit implémenter une méthode" sent la mauvaise architecture parce que la classe de base n'a évidemment pas besoin de cette méthode alors - alors pourquoi est-elle dans la classe de base en premier lieu?


Ensuite, gardez simplement la méthode de suppression vide dans la classe dérivée. Dans une bonne architecture, les méthodes abstraites sont déjà appelées dans la classe base (!). Ce que vous expliquez est un cas d'utilisation des interfaces (DeletableInterface, WritableInterface, etc.)


@Foobar qui n'a pas de méthodes abstraites fonctionne. Vous DEVEZ les implémenter ou vous serez renvoyé une erreur fatale. Vous lancez une exception pour garantir au développeur que cette méthode ne doit pas être appelée.


Dans l'exemple Symfony, la méthode execute est en fait appelée dans la classe de base. Ainsi, vous devez quand même étendre la classe de base et implémenter la méthode de toute façon.


Pourquoi une classe de base devrait-elle fournir une méthode qu'elle n'utilise pas elle-même et qui n'a pas à être implémentée dans les classes enfants? C'est du code mort alors.


Que faire si vous écrasez la méthode qui est censée appeler la méthode abstraite. alors vous n'avez pas besoin d'une implémentation de la méthode abstraite.


Sonne comme du code spaghetti. Évidemment, je n'ai pas besoin de la logique des classes de base si je l'écrase pour ne pas utiliser la logique qu'elle avait à l'origine. Ensuite, n'étendez pas la classe en premier lieu, mais utilisez une propre classe.


Je n'en discute plus. Désolé. Je suis vraiment ravi de voir si quelqu'un peut vous convaincre que vous n'avez qu'à le faire dans certains cas.


Merci quand même. :) Vous avez essayé de répondre à la question avec des exemples qui n'utilisent ces méthodes pseudo-abstraites que pour "résoudre" d'autres anti-patterns architecturaux. Mais je recherche une explication qui montre un cas d'utilisation réel qui est utile en soi sans qu'il soit nécessaire de violer d'abord un autre modèle.


@Foobar si vous regardez réellement le bloc doc de la méthode que vous avez fourni comme exemple, vous pouvez voir pourquoi l'auteur n'a pas marqué la méthode comme abstraite.


vous définissez le code à exécuter en passant un Closure à la méthode setCode ().


Oui, l'auteur y déclare qu'on peut instancier la classe - je peux le voir en voyant simplement qu'elle n'est pas marquée comme abstraite. Il n’explique toujours rien . C'est ce que je voulais dire. Je peux instancier un objet de cette classe mais je ne peux pas l'utiliser (parce que les méthodes que je pourrais appeler appellent implicitement les pseudo-abstracts de toute façon).


Instancier une classe qui fournit déjà un mécanisme, ne pas utiliser ce mécanisme, mais passer une fermeture - est-ce une bonne architecture? Je ne pense pas. Je peux voir comment cette classe est implémentée, mais j'ai demandé une explication et des avantages sans utiliser d'anti-modèles comme justification.


bien sûr, vous pouvez l'utiliser! et exécuter pourrait en fait ne pas être appelé du tout! \ Symfony \ Component \ Console \ Command \ statusCode :: 243


bien sûr, c'est une bonne architecture?! désolé, mais qui êtes-vous pour juger cela comme mauvais ...


S'il vous plaît, ne soyez pas personnel (à ce niveau, nous ne sommes en fait pas tous les deux pour juger si c'est un bon ou un mauvais design, au fait, alors restez sur le sujet). Une classe qui fournit un moyen de contourner la conception OO en fournissant un moyen d'obtenir simplement une procédure (en passant une fermeture) n'est pas OO. Mais cela sort du sujet. C'était en fait de ma faute de sélectionner un exemple qui fournit autant d'espace cible pour éviter de répondre à la question générique réelle (je n'ai pas demandé d'expliquer cet exemple unique de Symfony, je l'ai utilisé comme exemple).


mais cet exemple est un cas d'utilisation parfait! Je suis à 100% avec vous que vous devriez éviter de lancer des exceptions au lieu de le rendre abstrait. Mais parfois, c'est la seule façon de le résoudre.


Mais même dans cet exemple, est-ce vraiment le seul (ou le meilleur?) Moyen de le résoudre? L'option de fermeture aurait pu être fournie par une classe dérivée implémentant execute pour appeler la fermeture fournie. Ou le comportement par défaut de execute pourrait être d'appeler la fermeture, vous pourriez alors toujours remplacer execute dans une classe enfant.



1
votes

Dans ce cas particulier, les commentaires expliquent:

/**
 * Executes the current command.
 *
 * This method is not abstract because you can use this class
 * as a concrete class. In this case, instead of defining the
 * execute() method, you set the code to execute by passing
 * a Closure to the setCode() method.
 *
 * @return int|null null or 0 if everything went fine, or an error code
 *
 * @throws LogicException When this abstract method is not implemented
 *
 * @see setCode()
 */
protected function execute(InputInterface $input, OutputInterface $output)
{
    throw new LogicException('You must override the execute() method in the concrete command class.');
}

Vous pourriez discuter avec le design général car il est peut-être un peu piraté mais cela fonctionne bien en pratique. Jetez un œil à Command :: run pour voir où la décision d'utiliser une fermeture ou une exécution est prise. Un cas de niche pour dire le moins.

Je sais qu'un peu de cela a été discuté dans les commentaires de l'autre réponse, mais j'ai pensé que cela pourrait aider à résumer. J'ai également fait une recherche rapide dans le code du framework Symfony pour voir où l'approche de fermeture était utilisée. N'a rien trouvé. La prise en charge de la fermeture remonte à la version 2.0 originale, donc il se peut que l'un de ces éléments de fonctionnalité "semblait une bonne idée à l'époque".


0 commentaires

1
votes

Un cas d'utilisation possible est celui des méthodes optionnelles. Si vous le rendez abstrait, toutes les classes enfants doivent l'implémenter:

$s = new MySQL();
$s->export();

Erreur fatale: la classe MySQL contient 1 méthode abstraite et doit donc être déclarée abstraite ou implémenter les méthodes restantes (Database :: export)

Si vous créez une méthode régulière, les classes enfants n'ont besoin d'implémenter la méthode que si elles prévoient de la supporter.

abstract class Database
{
    public function export(){
        throw new \LogicException(__CLASS__ . ' driver does not support ' . __FUNCTION__);
    }
}

class MySQL extends Database
{
}

... mais vous obtenez une belle erreur si vous essayez de l'utiliser:

abstract class Database
{
    abstract public function export();
}

class MySQL extends Database
{
}

Erreur fatale: exception non interceptée 'LogicException': le pilote MySQL ne prend pas en charge l'exportation


5 commentaires

Mais n'est-ce pas une raison pour utiliser les interfaces?


@Foobar Erreur fatale: la fonction d'interface Database :: export () ne peut pas contenir de corps . Je ne sais pas si je manque quelque chose.


Je voulais dire, si vous ne voulez pas "être exportable", n'implémentez pas l ' interface Exportable . Pour que la méthode "non prise en charge" ne soit pas là en premier lieu. La classe MySQL étend les implémentations de base de données exportables pour prendre en charge l'exportation, et sans implémente exportables quand ce n'est pas le cas.


Cette technique est correcte mais elle ne s'adapte pas car une classe ne peut implémenter que zéro ou une interface. Je suppose que vous pouvez créer des traits à la place. Cependant, il existe des tonnes de code pré-PHP / 5.4, ce qui explique souvent de nombreuses solutions de contournement étranges.


Une classe peut implémenter autant d'interfaces qu'elle le souhaite (ok, techniquement, je suppose qu'il y a des limites à la quantité de milliards qu'un entier peut contenir sur l'architecture). Il ne peut hériter que d'une seule classe ( extend ne peut être utilisé qu'une seule fois par classe, mais les implements peuvent en contenir autant qu'il le souhaite). Et l'argument PHP 5.4 ne tient pas vraiment, car je viens de commencer à voir ce modèle "pseudo-abstrait" dans les plus jeunes temps.