Supposons que nous ayons une liste d'entités de tâche et un ProjectTask
sous-type. Les tâches peuvent être fermées à tout moment, à l'exception des tâches ProjectTasks
qui ne peuvent pas être fermées une fois qu'elles ont le statut Lancé. L’interface utilisateur doit s’assurer que l’option de fermeture d’un espace ouvert ProjectTask
n’est jamais disponible, mais certaines garanties sont présentes dans le domaine:
public class Task
{
public Status Status { get; set; }
public virtual void Close()
{
Status = Status.Closed;
}
}
public class ProjectTask : Task
{
public override void Close()
{
if (Status == Status.Started)
throw new Exception("Cannot close a started Project Task");
base.Close();
}
}
Désormais, lors de l'appel Close()
d'une tâche, il est possible que l'appel échoue s'il s'agit d'un ProjectTask
état démarré, alors que ce ne serait pas le cas s'il s'agissait d'une tâche de base. Mais ce sont les exigences commerciales. Cela devrait échouer. Cela peut-il être considéré comme une violation du principe de substitution de Liskov ?
la source
public Status Status { get; private set; }
; sinon, laClose()
méthode peut être contournée.Task
n’introduisent pas d’incompatibilités bizarres dans le code polymorpheTask
. Le LSP n'est pas un caprice, mais a été introduit précisément pour faciliter la maintenance dans les grands systèmes.TaskCloser
processus quiclosesAllTasks(tasks)
. Ce processus ne tente évidemment pas d'attraper les exceptions; après tout, cela ne fait pas partie du contrat explicite deTask.Close()
. Maintenant, vous introduisezProjectTask
et, tout à coup,TaskCloser
commence à lancer des exceptions (éventuellement non gérées). Ceci est une grosse affaire!Réponses:
Oui, c'est une violation du LSP. Le principe de substitution de Liskov exige que
Votre exemple casse la première condition en renforçant une condition préalable à l'appel de la
Close()
méthode.Vous pouvez résoudre ce problème en amenant la condition préalable renforcée au niveau supérieur de la hiérarchie d'héritage:
En stipulant qu'un appel de
Close()
n'est valide que dans l'état où lesCanClose()
retourstrue
vous font que la condition préalable s'applique à laTask
ainsi qu'à laProjectTask
, réparant la violation LSP:la source
Close
faire vérifier le niveau supérieur et ajouter un protégéDoClose
serait une alternative valable. Cependant, je voulais rester aussi proche que possible de l'exemple du PO; l’améliorer est une question distincte.Oui. Cela viole le LSP.
Ma suggestion est d'ajouter une
CanClose
méthode / propriété à la tâche de base afin que toute tâche puisse indiquer si une tâche dans cet état peut être fermée. Cela peut aussi fournir une raison. Et retirez le virtuel deClose
.Basé sur mon commentaire:
la source
Le principe de substitution de Liskov stipule qu'une classe de base doit pouvoir être remplacée par l'une de ses sous-classes sans modifier les propriétés souhaitables du programme. Etant donné que ne
ProjectTask
soulève une exception à la fermeture, un programme devrait être modifié pour tenir compte de cela, il devraitProjectTask
être utilisé à la place deTask
. C'est donc une violation.Mais si vous modifiez en
Task
indiquant dans sa signature qu'il peut déclencher une exception à la fermeture, vous ne violeriez pas le principe.la source
Une violation de LSP nécessite trois parties. Le type T, le sous-type S et le programme P qui utilise T mais se voit attribuer une instance de S.
Votre question a fourni T (tâche) et S (tâche de projet), mais pas P. Donc, votre question est incomplète et la réponse est qualifiée: s'il existe un P qui ne s'attend pas à une exception, alors, pour ce P, vous avez un LSP violation. Si chaque P s'attend à une exception, il n'y a pas de violation LSP.
Cependant, vous n'avez une SRP violation. Le fait que l'état d'une tâche puisse être modifié et la politique selon laquelle certaines tâches dans certains États ne doivent pas être modifiées pour devenir d'autres États sont deux responsabilités très différentes.
Ces deux responsabilités changent pour des raisons différentes et doivent donc être réparties dans des classes distinctes. Les tâches doivent gérer le fait d'être une tâche et les données associées à une tâche. TaskStatePolicy doit gérer la manière dont les tâches passent d'un état à l'autre dans une application donnée.
la source
OpenTaskException
(indice, indice) et que chaque P s'attend à une exception, que dit-il du code à l'interface, et non de la mise en œuvre? De quoi est-ce que je parle? Je ne sais pas. Je suis juste convaincu que je commente une réponse de Unca 'Bob.Cela peut ou peut ne pas être une violation du LSP.
Sérieusement. Écoutez-moi.
Si vous suivez le LSP, les objets de type
ProjectTask
doivent se comporter comme des objets de typeTask
doivent se comporter.Le problème avec votre code est que vous n'avez pas documenté la manière dont les objets de type
Task
doivent se comporter. Vous avez écrit du code, mais pas de contrat. Je vais ajouter un contrat pourTask.Close
. Selon le contrat que j'ajoute, le codeProjectTask.Close
correspondant ou non au LSP.Compte tenu du contrat suivant pour Task.Close, le code de
ProjectTask.Close
ne suit pas le LSP:Étant donné le contrat suivant pour Task.Close, le code pour
ProjectTask.Close
ne suivre le LSP:Les méthodes pouvant être remplacées doivent être documentées de deux manières:
Le "Comportement" décrit ce sur quoi un client peut compter, sachant que l’objet destinataire est un
Task
, mais ne sait pas de quelle classe il est une instance directe. Il indique également aux concepteurs des sous-classes quelles substitutions sont raisonnables et lesquelles ne le sont pas.Le "comportement par défaut" décrit ce à quoi un client peut se fier et sait que l'objet destinataire est une instance directe de
Task
(ce que vous obtenez si vous l'utiliseznew Task()
. Il indique également aux concepteurs des sous-classes quel comportement sera hérité s'ils ne le font pas. remplacer la méthode.Maintenant, les relations suivantes devraient tenir:
la source
Close
throw". Donc, la signature déclare qu'une exception peut être levée - elle ne dit pas que ce ne sera pas le cas. Java fait un meilleur travail à cet égard. Même dans ce cas, si vous déclarez qu'une méthode peut déclarer une exception, vous devez documenter les circonstances dans lesquelles elle peut (ou le fera). Je soutiens donc que pour être sûr que le LSP soit violé, nous avons besoin de documentation au-delà de la signature.if (false) throw new Exception("cannot start")
à la classe de base. Le compilateur va le supprimer, et le code contient toujours ce qui est nécessaire. Btw. nous avons toujours une violation LSP avec ces solutions de contournement, car la condition préalable est encore renforcée ...Ce n'est pas une violation du principe de substitution de Liskov.
Le principe de substitution de Liskov dit:
La raison pour laquelle votre implémentation du sous-type n'est pas une violation du principe de substitution de Liskov est très simple: rien ne peut être prouvé sur ce qui
Task::Close()
se passe réellement. Bien sûr,ProjectTask::Close()
jette une exception quandStatus == Status.Started
, mais pourrait le faireStatus = Status.Closed
dansTask::Close()
.la source
Oui, c'est une violation.
Je suggérerais que vous ayez votre hiérarchie à l'envers. Si tout
Task
n'est pas fermable, alorsclose()
n'appartient pas àTask
. Peut-être que vous voulez une interface,CloseableTask
que tous les non-ProjectTasks
peuvent implémenter.la source
Task
elle-même n’implémente pas,CloseableTask
ils font un casting peu sûr, même quelque partClose()
.En plus d'être un problème lié au fournisseur de services linguistiques, il semble qu'il utilise des exceptions pour contrôler le flux de programmes (je dois supposer que vous capturez cette exception triviale quelque part et effectuez un flux personnalisé plutôt que de le laisser bloquer votre application).
Il semble que ce soit un bon endroit pour implémenter le modèle d'état pour TaskState et laisser les objets d'état gérer les transitions valides.
la source
Il me manque ici un élément important lié aux prestataires de services linguistiques et à la conception par contrat - dans les conditions préalables, c’est à l’appelant qui a la responsabilité de s’assurer que les conditions préalables sont remplies. Le code appelé, en théorie DbC, ne devrait pas vérifier la condition préalable. Le contrat doit spécifier quand une tâche peut être fermée (par exemple, CanClose renvoie True), puis le code appelant doit garantir que la condition préalable est remplie avant d'appeler Close ().
la source
ProjectTask
. C'est une post-condition (cela dit ce qui se passe après l'appel de la méthode) et sa réalisation incombe au code appelé.Oui, c'est une violation claire de LSP.
Certaines personnes soutiennent ici que rendre explicite dans la classe de base que les sous-classes peuvent renvoyer des exceptions rendrait cela acceptable, mais je ne pense pas que ce soit le cas. Peu importe ce que vous documentez dans la classe de base ou le niveau d'abstraction vers lequel vous déplacez le code, les conditions préalables seront toujours renforcées dans la sous-classe, car vous y ajoutez la partie "Impossible de fermer une tâche de projet commencée". Ce problème ne peut pas être résolu par une solution de contournement, il vous faut un modèle différent, qui ne viole pas le LSP (ou il faut relâcher la contrainte "les conditions préalables ne peuvent pas être renforcées").
Vous pouvez essayer le motif de décorateur si vous souhaitez éviter la violation LSP dans ce cas. Cela pourrait fonctionner, je ne sais pas.
la source