Cette image est tirée de l' application de modèles et de modèles pilotés par domaine: avec des exemples en C # et .NET
Il s'agit du diagramme de classes du modèle d'état dans lequel un état SalesOrder
peut avoir différents états au cours de sa durée de vie. Seules certaines transitions sont autorisées entre les différents états.
Maintenant, la OrderState
classe est une abstract
classe et toutes ses méthodes sont héritées de ses sous-classes. Si nous considérons la sous-classe Cancelled
qui est un état final qui n'autorise aucune transition vers d'autres états, nous devrons utiliser override
toutes ses méthodes dans cette classe pour lever des exceptions.
Maintenant, cela ne viole-t-il pas le principe de substitution de Liskov, car une sous-classe ne devrait pas modifier le comportement du parent? La modification de la classe abstraite en interface résout-elle cela?
Comment résoudre ce problème?
cancel()
change l'état en annulé.Réponses:
Cette implémentation particulière, oui. Si vous faites des états des classes concrètes plutôt que des implémenteurs abstraits, vous vous en sortirez.
Cependant, le modèle d'état auquel vous faites référence, qui est en fait une conception de machine d'état, est en général quelque chose avec lequel je ne suis pas d'accord avec la façon dont je l'ai vu grandir. Je pense qu'il existe des motifs suffisants pour dénoncer cela comme une violation du principe de responsabilité unique, car ces schémas de gestion de l'État finissent par être des référentiels centraux pour la connaissance de l'état actuel de nombreuses autres parties d'un système. Cette partie de la gestion de l'état étant centralisée nécessitera le plus souvent des règles commerciales pertinentes pour de nombreuses parties différentes du système pour les coordonner de manière raisonnable.
Imaginez si toutes les parties du système qui se soucient de l'état se trouvaient dans différents services, différents processus sur différentes machines, un gestionnaire d'état central qui détaille l'état de chacun de ces endroits goulot d'étranglement efficace de tout ce système distribué et je pense que le goulot d'étranglement est un signe d'une violation SRP ainsi que d'une conception généralement mauvaise.
En revanche, je suggérerais de rendre les objets plus intelligents comme dans l'objet Model dans le modèle MVC où le modèle sait comment se gérer, il n'a pas besoin d'un orchestrateur externe pour gérer son fonctionnement interne ou sa raison.
Même en plaçant un modèle d'état comme celui-ci à l'intérieur d'un objet afin qu'il ne se gère que lui-même, il semble que vous rendriez cet objet trop grand. Les workflows devraient être effectués par la composition de divers objets auto-responsables, je dirais, plutôt qu'avec un seul état orchestré qui gère le flux d'autres objets, ou le flux d'intelligence en lui-même.
Mais à ce stade, c'est plus de l'art que de l'ingénierie et il est donc certainement subjectif de votre approche de certaines de ces choses, cela dit que les principes sont un bon guide et oui l'implémentation que vous listez est une violation LSP, mais pourrait être corrigée de ne pas l'être. Faites très attention au SRP lorsque vous utilisez n'importe quel modèle de cette nature et vous serez probablement en sécurité.
la source
C'est une mauvaise interprétation courante de LSP. Une sous-classe peut modifier le comportement du parent, tant qu'elle reste fidèle au type de parent.
Il y a une bonne longue explication sur Wikipedia , suggérant les choses qui violeront le LSP:
Personnellement, je trouve plus facile de simplement me souvenir de ceci: si je regarde un paramètre dans une méthode qui a le type A, est-ce que quelqu'un qui passe un sous-type B me surprendra? S'ils le faisaient, il y aurait alors violation du LSP.
Lancer une exception est-il une surprise? Pas vraiment. C'est quelque chose qui peut arriver à tout moment, que j'appelle la méthode Ship sur OrderState ou Granted ou Shipped. Je dois donc en tenir compte et ce n'est pas vraiment une violation de LSP.
Cela dit , je pense qu'il existe de meilleures façons de gérer cette situation. Si j'écrivais cela en C #, j'utiliserais des interfaces et vérifierais l'implémentation d'une interface avant d'appeler la méthode. Par exemple, si le OrderState actuel n'implémente pas IShippable, n'appelez pas de méthode Ship dessus.
Mais je n'utiliserais pas non plus le modèle d'État pour cette situation particulière. Le modèle d'état est beaucoup plus approprié à l'état d'une application qu'à l'état d'un objet de domaine comme celui-ci.
Donc, en un mot, c'est un exemple mal conçu du modèle d'état et pas un moyen particulièrement bon de gérer l'état d'une commande. Mais cela ne contrevient sans doute pas au LSP. Et le modèle d'État, en soi, ne l'est certainement pas.
la source
CircleWithFixedCenterButMutableRadius
comme une violation probable du LSP si le contrat de consommation pourImmutablePoint
spécifie que siX.equals(Y)
, les consommateurs peuvent librement substituer X à Y, et vice versa, et doivent donc s'abstenir d'utiliser la classe de telle manière qu'une telle substitution causerait des problèmes. Le type pourrait être en mesure de définir légitimement deequals
sorte que toutes les instances se compareraient comme distinctes, mais un tel comportement limiterait probablement son utilité.(Ceci est écrit du point de vue de C #, donc pas d'exceptions vérifiées.)
Selon un article de Wikipedia sur le LSP , l'une des conditions du LSP est:
Comment devez-vous comprendre cela? Que sont exactement les «exceptions levées par les méthodes du supertype» lorsque les méthodes du supertype sont abstraites? Je pense que ce sont les exceptions qui sont documentées comme les exceptions qui peuvent être levées par les méthodes du supertype.
Cela signifie que si
OrderState.Ship()
est documenté comme quelque chose comme «lanceInvalidOperationException
si ces opérations ne sont pas prises en charge par l'état actuel», alors je pense que cette conception ne rompt pas le LSP. D'un autre côté, si les méthodes de supertype ne sont pas documentées de cette façon, LSP est violé.Mais cela ne signifie pas que c'est une bonne conception, vous ne devez pas utiliser d'exceptions pour le flux de contrôle normal, ce qui semble très proche. De plus, dans une application réelle, vous voudrez probablement savoir si une opération peut être effectuée avant de tenter de le faire, par exemple pour désactiver le bouton «Expédier» dans l'interface utilisateur.
la source