web-dev-qa-db-fra.com

Les cas spéciaux avec des retombées violent-ils le principe de substitution de Liskov?

Disons que j'ai une interface FooInterface qui a la signature suivante:

interface FooInterface {
    public function doSomething(SomethingInterface something);
}

Et une classe concrète ConcreteFoo qui implémente cette interface:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
    }

}

Je voudrais ConcreteFoo::doSomething() Pour faire quelque chose d'unique s'il est passé un type spécial de SomethingInterface objet (disons qu'elle s'appelle SpecialSomething).

C'est définitivement une violation du LSP si je renforçais les conditions préalables de la méthode ou jetez une nouvelle exception, mais il s'agirait-il d'une violation de la LSP si je suis spécialisé SpecialSomething objets tout en fournissant une fuite pour générique SomethingInterface objets? Quelque chose comme:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
        if (something instanceof SpecialSomething) {
            // Do SpecialSomething magic
        }
        else {
            // Do generic SomethingInterface magic
        }
    }

}
20
Evan

Il pourrait une violation de la LSP en fonction de quoi d'autre est dans le contrat de la méthode doSomething. Mais c'est presque certainement une odeur de code même si cela ne violait pas le LSP.

Par exemple, si une partie du contrat de doSomething est qu'il appellera something.commitUpdates() au moins une fois avant de retourner, et pour le cas particulier, il appelle commitSpecialUpdates(). cela est une violation du LSP. Même si SpecialSomething 's commitSpecialUpdates() méthode a été consciemment conçue pour faire tout le même contenu que commitUpdates(), ce qui vient de pirater de manière préemptive autour de la violation du LSP et est exactement le genre de Le pirate de hackerie n'aurait pas à faire si on a suivi le LSP systématiquement. Que ce soit comme si ceci s'applique à votre cas est quelque chose que vous devrez déterminer en vérifiant votre contrat pour cette méthode (explicite ou implicite).

La raison en est une odeur de code est que la vérification du type concret de l'un de vos arguments manque le point de définir une interface/type abstrait pour cela en premier lieu, et parce que, en principe, vous ne pouvez plus garantir que la méthode fonctionne même (imaginez Si quelqu'un écrit une sous-classe de SpecialSomething avec l'hypothèse que commitUpdates() sera appelée). Tout d'abord, essayez de faire fonctionner ces mises à jour spéciales dans l'existant SomethingInterface; C'est le meilleur résultat possible. Si vous êtes vraiment sûr de ne pas pouvoir le faire, vous devez mettre à jour l'interface. Si vous ne contrôlez pas l'interface, vous devrez peut-être envisager d'écrire votre propre interface qui fait ce que vous voulez. Si vous ne pouvez même pas trouver une interface qui fonctionne pour tous, vous devriez peut-être supprimer l'interface entièrement et avoir plusieurs méthodes prenant différents types de béton, ou peut-être un refacteur encore plus grand est en ordre. Nous devrions en savoir plus sur la magie que vous avez commentée pour dire laquelle d'entre elles est appropriée.

19
Ixrec

Non, profiter du fait qu'un argument donné ne fournit pas seulement l'interface A, mais également A2, ne violait pas le LSP.

Assurez-vous simplement que le chemin spécial n'a pas de conditions préalables plus fortes (à part ceux testés pour décider de le prendre), ni des postes plus faibles.

Les modèles C++ le font souvent de fournir de meilleures performances, par exemple en exigeant InputIterators, mais en donnant des garanties supplémentaires si elles sont appelées RandomAccessIterators.

Si vous devez décider au moment de l'exécution (par exemple, à l'aide de la coulée dynamique), méfiez-vous de la décision que la voie de prendre la consommation de tous vos gains potentiels ou encore plus.

Tirer parti du cas spécial va souvent contre DRY (Ne vous répétez pas vous-même), comme vous pourriez avoir à dupliquer le code et contre KISS (Gardez-le Simple), comme il est plus complexe.

1
Deduplicator

Ce n'est pas une violation du LSP. Cependant, il s'agit toujours d'une violation de la règle "Ne pas faire de type Checks". Il serait préférable de concevoir le code de manière à ce que la spéciale se présente naturellement; Peut-être que SomethingInterface a besoin d'un autre membre qui pourrait y accomplir, ou peut-être avez-vous besoin d'injecter une usine abstraite quelque part.

Cependant, ce n'est pas une règle difficile et rapide, vous devez donc décider si le compromis en vaut la peine. À l'heure actuelle, vous avez une odeur de code et un obstacle possible aux améliorations futures. Se débarrasser de cela pourrait signifier une architecture considérablement plus compliquée. Sans plus d'informations, je ne peux pas dire lequel est le meilleur.

1
Sebastian Redl

Il existe un compromis entre le principe de "Ne pas faire de type de vérification" et "séparer vos interfaces". Si de nombreuses classes fournissent un moyen de réaliser des tâches viables mais éventuellement inefficaces, et quelques-uns d'entre eux peuvent également offrir un meilleur moyen, et il existe un besoin de code qui peut accepter l'une des catégories plus larges d'éléments pouvant effectuer la tâche (Peut-être inefficacement), puis effectuez la tâche aussi efficace que possible, il sera nécessaire de disposer de tous les objets d'implémenter une interface qui inclut un membre pour dire si la méthode la plus efficace est prise en charge et une autre pour l'utiliser si elle est, ou bien Demandez au code qui reçoit l'objet Vérifiez s'il prend en charge une interface étendue et la jette si elle.

Personnellement, je suis favorable à l'ancienne approche, bien que je souhaite que les cadres orientés objet tels que .NET permettrait aux interfaces de spécifier des méthodes par défaut (rendant les plus grandes interfaces moins douloureuses au fonctionnement). Si l'interface commune comprend des méthodes facultatives, une seule classe d'enveloppe peut gérer des objets avec de nombreuses combinaisons différentes de capacités tout en promettant pour les consommateurs que ces capacités présentes dans l'objet enveloppé d'origine. Si de nombreuses fonctions sont divisées en différentes interfaces, un objet d'emballage différent sera nécessaire pour chaque combinaison différente d'interfaces que les objets emballés devraient être pris en charge.

0
supercat

Le principe de substitution de Liskov concerne les sous-types agissant conformément à un contrat de leur supertype. Ainsi, comme Ixrec écrivit, il n'ya pas assez d'informations pour répondre à la violation du LSP.

Ce qui est violé ici, c'est un principe fermé ouvert. Si vous avez une nouvelle exigence - faites de la magie de SPECIAINEOMPHOOREMENT - et vous devez modifier le code existant, alors vous êtes violation définitivement OCP .

0
Zapadlo