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) {
}
}
J'aimerais ConcreteFoo::doSomething()
faire quelque chose d'unique s'il passe un type d' SomethingInterface
objet spécial (disons qu'il s'appelle SpecialSomething
).
C'est définitivement une violation LSP si je renforce les conditions préalables de la méthode ou si je lève une nouvelle exception, mais serait-ce toujours une violation LSP si je casse des SpecialSomething
objets spéciaux tout en fournissant une solution de secours pour les SomethingInterface
objets génériques ? Quelque chose comme:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
méthode serait de convertir le type enSpecialSomething
: si elle le recevait,SpecialSomething
elle retournerait simplement l'objet non modifié, tandis que si elle recevait unSomethingInterface
objet générique , elle exécuterait un algorithme pour le convertir enSpecialSomething
objet. Étant donné que les conditions préalables et postconditions restent les mêmes, je ne pense pas que le contrat ait été violé.y = doSomething(x)
,x.setFoo(3)
puis de trouver ley.getFoo()
résultat 3?SpecialSomething
objet à la place. Bien que pour des raisons de pureté, je pouvais également voir renoncer à l'optimisation du cas spécial lorsque l'objet passé estSpecialSomething
et simplement l'exécuter à travers l'algorithme de conversion plus grand, car il devrait toujours fonctionner car il est également unSomethingInterface
objet.Ce n'est pas une violation du LSP. Cependant, il s'agit toujours d'une violation de la règle "ne faites pas de vérification de type". Il serait préférable de concevoir le code de telle manière que le spécial se pose naturellement; peut-être a
SomethingInterface
besoin d'un autre membre qui pourrait accomplir cela, ou peut-être que vous devez injecter une usine abstraite quelque part.Cependant, ce n'est pas une règle stricte 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. S'en débarrasser pourrait signifier une architecture beaucoup plus compliquée. Sans plus d'informations, je ne peux pas dire laquelle est la meilleure.
la source
Non, profiter du fait qu'un argument donné ne fournit pas seulement l'interface A, mais aussi A2, ne viole pas le LSP.
Assurez-vous simplement que le chemin spécial n'a pas de conditions préalables plus fortes (à part celles testées pour décider de le prendre), ni de conditions postérieures plus faibles.
Les modèles C ++ le font souvent pour fournir de meilleures performances, par exemple en exigeant
InputIterator
s, mais en donnant des garanties supplémentaires s'ils sont appelés avecRandomAccessIterator
s.Si vous devez plutôt décider au moment de l'exécution (par exemple à l'aide de la diffusion dynamique), méfiez-vous de la décision à prendre pour consommer tous vos gains potentiels ou même plus.
Profiter du cas spécial va souvent à l'encontre de DRY (ne vous répétez pas), car vous pourriez avoir à dupliquer du code, et à KISS (Keep it Simple), car il est plus complexe.
la source
Il existe un compromis entre le principe de «ne pas faire de vérification de type» et de «séparer vos interfaces». Si de nombreuses classes fournissent un moyen réalisable mais éventuellement inefficace d'effectuer certaines tâches, et que certaines d'entre elles peuvent également offrir un meilleur moyen, et il existe un besoin de code qui peut accepter n'importe quelle catégorie plus large d'éléments pouvant effectuer la tâche (peut-être de manière inefficace) mais ensuite effectuer la tâche aussi efficacement que possible, il sera nécessaire que tous les objets implémentent 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 c'est le cas, ou bien demander au code qui reçoit l'objet de vérifier s'il prend en charge une interface étendue et le cas échéant.
Personnellement, je privilégie la première approche, bien que je souhaite que les frameworks orientés objet comme .NET permettent aux interfaces de spécifier des méthodes par défaut (rendant les interfaces plus grandes moins difficiles à utiliser). Si l'interface commune comprend des méthodes facultatives, une seule classe wrapper peut gérer des objets avec de nombreuses combinaisons de capacités différentes tout en ne promettant aux consommateurs que les capacités présentes dans l'objet wrappé d'origine. Si de nombreuses fonctions sont divisées en différentes interfaces, un objet wrapper différent sera nécessaire pour chaque combinaison d'interfaces différentes que les objets wrappés pourraient avoir besoin de prendre en charge.
la source
Le principe de substitution de Liskov concerne les sous-types agissant conformément à un contrat de leur supertype. Ainsi, comme l'a écrit Ixrec, il n'y a pas suffisamment d'informations pour savoir s'il s'agit d'une violation LSP.
Ce qui est violé ici cependant, c'est un principe ouvert et fermé. Si vous avez une nouvelle exigence - faire de la magie spéciale - et que vous devez modifier le code existant, vous violez définitivement OCP .
la source