Timeline for Does deciding about Liskov sub.princple compliance require a contract?
Current License: CC BY-SA 4.0
55 events
when toggle format | what | by | license | comment | |
---|---|---|---|---|---|
Jul 27, 2018 at 12:59 | comment | added | John V | I think the OP wanted to express the following: in a base class, a method requires (from a contract view) a value that is within a certain range. In the overriden method, it will require a bit more to achieve the same postcondition and that is indeed breaking LSP as the code working with the base class object will not work with the subtype object. If he used Java annotations instead of a code resembling flow processing, it would be much clearer. | |
Jul 27, 2018 at 12:50 | comment | added | John V | Agree, internal logic has nothing to do with that. When I say it violates LSP I mean it when I consider those values to form a precondition. If the base method accepted Live and expected it to be greater than 50,the overriding method requesting it to be greater than 100 would be a glaring violation. (Corrected a mistake in edit) | |
Jul 27, 2018 at 12:43 | comment | added | Flater | @user970696: In OP's case: The input is not being validated more strictly. The possible outcomes remain the same (none were added or removed). The only thing that was changed is encapsulated logic inside of the method. That is as encapsulated as it can ever be. If you don't allow method bodies to be different even when they are not changing in- or output states, then you're effectively saying that methods should never be overridden and then LSP (and inheritance) just goes out the window altogether. | |
Jul 27, 2018 at 12:40 | comment | added | Flater |
@user970696: Your example still uses an input parameter - I agree that input parameter validity should not be changed. I agree with you in theory. If OP's method were a simple ExecuteDispose() , it would be wrong for the Boss to refuse to dispose for a reason that does not apply to Enemy . However, that's not what OP has. OP has written a method which is tasked with independently deciding to do something. When he overrides that method, it too needs to independently make a decision. Changing the decision making process (without changing the possible outcomes) is not a violation of LSP
|
|
Jul 27, 2018 at 12:37 | comment | added | John V | LSP is broken not only when you have to change the client code but also when the behavior is not consistent with what you expect based on the base class behavior. If the arguments (or values of instance variables) are sufficient for the parent, they must be sufficient also for the child to achieve the same postcondition. When a method accepts char[10] then its overridden version must not accept char[9]. Not sure I can explain better but I did write a paper about LSP in German. | |
Jul 27, 2018 at 12:22 | comment | added | Flater |
@user970696: Value of a parameter is probably the most common precondition along with a null reference check. Of an input parameter. Not just any state variable of the object You can't just define a precondition as "anything that can be checked", so where do you draw the line? The context helps draw that line. OP's method clearly intends for the method to internally decide if it needs to do something. Regardless of the decision made, the calling code can continue. There is no violation of LSP as the calling code does not need to change its handling of a Boss which is cast to Enemy .
|
|
Jul 27, 2018 at 12:16 | comment | added | John V | If I even get noticed: It is not unusual to have a precondition verified in the callee. Value of a parameter is probably the most common precondition along with a null reference check. Overridden method must not require more than its base version which is why OP broke the principle. | |
Jul 27, 2018 at 12:03 | comment | added | Ezoela Vacca | @Flater They definitely should, as having somebody here who is smarter than Liskov and openly claims she is wrong in her theory..If you ever checked the original LSP, you would never had claimed that. Bye | |
Jul 27, 2018 at 12:02 | comment | added | Flater | @EzoelaVacca: I'm really just responding so that the mods move this entire shitshow to chat. I'm surprised they haven't already. | |
Jul 27, 2018 at 12:00 | comment | added | Ezoela Vacca | Ah, so Liskov is wrong in her example, where a method precondition is a value of the argument being > 0. Right..what could she know about that.. | |
Jul 27, 2018 at 11:57 | comment | added | Ezoela Vacca | Then you should go and downvote the answers in SO which claim that with a base precondition 0<100, the child precondition 0<50 is strengthened. This is my only point. | |
Jul 27, 2018 at 11:56 | comment | added | Ezoela Vacca | No, both Deduplicator and user970.. agree that LSP is violated, just the contract is not stated explicitly and it should be... | |
Jul 27, 2018 at 11:53 | comment | added | Flater | @EzoelaVacca: Copy/paste your comment again, see if that helps. Every point you raise has already been addressed several times by several people. Instead of re-reading the paper you misunderstand, maybe re-read the answers to your question. | |
Jul 27, 2018 at 11:49 | comment | added | Flater | @EzoelaVacca: It'd help if you actually tried to understand the answer instead of supplant it with your own ideas. I'm done with this. The top 3 answers all boil down to you misunderstanding the intention of LSP and still you argue that you understand it perfectly. You are not interested in a solution, you are only interested in being told you're right. No matter how much you rephrase your own idea, it remains flawed. End of discussion. | |
Jul 27, 2018 at 11:48 | comment | added | Ezoela Vacca | @Flater, I am not saying the check is the precondition itself, I just expressed it. It would be the same if I put a comment there saying "requires life<=0". | |
Jul 27, 2018 at 11:46 | history | edited | Flater | CC BY-SA 4.0 |
added 908 characters in body
|
Jul 27, 2018 at 11:46 | comment | added | Flater | @EzoelaVacca: Read the answers that are posted please. THE CHECK IS NOT A PRECONDITION. Regardless of what the paper says on preconditions, your check is not a precondition, and therefore the explanation given by the paper does not apply. We can't make it any more clear than that. | |
Jul 27, 2018 at 11:46 | comment | added | Ezoela Vacca | @Flater Read the paper by Liskov please. The called method assumes the precondition is met! Then imagine it is not an IF but Assert. And it is stronger in the subtype. In Liskov paper, the precondition for calling SetWidth is that the value of argument W>0. So you are saying Liskov is wrong. Because her strengthening example is that base requires W<=0, child requires W<-1 and that is a violation as the precondition is stronger. Even the MIT papers DO include precondition checks in the called method, as a means of defense programming. | |
Jul 27, 2018 at 11:41 | comment | added | Flater |
@EzoelaVacca I included the check so that it is visible what precondition is expected. It does not change the fact that the precondition is stronger in the derived class. This proves that you do not understand what RubberDuck meant. The check is not a precondition. Your example does not have a different precondition between Boss and Enemy. The calling code should not have any expectation of the life value, nor whether it passes the <0 check before calling the method. The entire point of the method is so that the method can be called without needing to pre-check.
|
|
Jul 27, 2018 at 11:30 | comment | added | Ezoela Vacca | Please check the highly voted answer here, showing several examples of LSP violation (runtime switching is one, as well as condition strengthening same as I have shown here): stackoverflow.com/questions/20861107/… | |
Jul 27, 2018 at 11:26 | comment | added | Ezoela Vacca | @Flater As I explained above, I understand that. The precondition normally (paraphrasing Liskov) is a value of an argument, arguments or instance members that must be true before the method is executed. In this case, you can simply think that life<=0 must hold before you call the method. Calling code should make sure that the precondition holds. But often in real world, the called code does check the precondition again (according to DbC e.g. when the called is an external code). | |
Jul 27, 2018 at 11:23 | comment | added | Ezoela Vacca | @RubberDuck I do understand that, I included the check so that it is visible what precondition is expected. It does not change the fact that the precondition is stronger in the derived class. I already found the answer in an original example from Liskov - in the base class, she used W<=0. In the child, W<-1, showing LSP violating in making the condition stronger. In most examples, the check is included with assert. | |
Jul 27, 2018 at 7:18 | history | edited | Flater | CC BY-SA 4.0 |
added 572 characters in body
|
Jul 27, 2018 at 7:17 | comment | added | Flater | @RubberDuck: That is a much better explanation than I've managed to give. I'll add that to the answer. | |
Jul 26, 2018 at 21:52 | comment | added | RubberDuck | @EzoelaVacca I believe you may be misunderstanding the meaning of “precondition”. A precondition is not the internal check you’ve shown in your example. A precondition is what must be true prior to calling a method in order to avoid exceptions/undefined behavior. In other words, a precondition is the state of the system prior to calling a method. It’s that state of the system that can not be made stricter. | |
Jul 26, 2018 at 13:58 | comment | added | Ezoela Vacca | @Flater So according to you the square and rectangle example, shown by Liskov herself, or Robert C. Martin, is incorrect? And the precondition there, explicitly stated, is that w>0... | |
Jul 26, 2018 at 13:56 | comment | added | Flater | @EzoelaVacca: If you apparently already know the answer, why ask the question? I'm done repeating myself. | |
Jul 26, 2018 at 13:55 | comment | added | Ezoela Vacca | In the famous LSP demonstration, Square and Rectangle, the client code does not handle anything differently, the results are just not what the caller expected (aa instead of ab). It just shows that the reasoning about behavior is crucial. | |
Jul 26, 2018 at 13:54 | comment | added | Flater | @EzoelaVacca: This question is pointless if you're just going to keep repeating your flawed interpretation. I have adequately and repeatedly explained the issue in your understanding of the intention of LSP. You define the value of an object's property as its behavior, which it is not. You break encapsulation and SRP and then claim LSP violations based on expectations that should have never existed in your client code. The issue isn't in your implementation of LSP but in your expectation of what is and isn't behavior. You can't judge violations if you don't understand the core intention of LSP | |
Jul 26, 2018 at 13:51 | comment | added | Ezoela Vacca | Yes, it breaks the external behavior and that is why I believe my example does violate LSP. The strengthened precondition will cause that the boss object will not get destroyed when the enemy object will, which is what the caller reason. | |
Jul 26, 2018 at 13:48 | comment | added | Flater |
@EzoelaVacca: Yes but it does break the behavior, which is the main point in LSP. External behavior, yes. Internal behavior, no. And that's the point. What the object does (or doesn't do) inside its method is irrelevant, as long as the external caller does not need to personally and explicitly account for any change in behavior. That is the crux of the issue here. You could create an enemy that is literally immortal and it still wouldn't violate LSP as long as it doesn't need to be handled differently by client code. Your alleged problem situations all violate encapsulation and SRP.
|
|
Jul 26, 2018 at 13:42 | comment | added | Ezoela Vacca | @Flater Yes but it does break the behavior, which is the main point in LSP. If I have a third party code, e.g. EnemyDestroyer accepting objects of type Enemy and destroying them by making their life=0, it will not work with the boss object as its condition is more strict, requiring additional value to be 0. This is what I mean. It will be OK for the parent but not for the child. How is that different from having parent accept 100 and child only 50 (=strengthening that violates LSP)? | |
Jul 26, 2018 at 13:38 | comment | added | Flater | @EzoelaVacca: I have repeatedly mentioned that the error is in blocking behavior such as throwing exceptions. I'm not sure how the issue you're bringing to the table conflicts with that. Your posted example doesn't even touch on changing accepted input values, since the method does not even have any input values. This current discussion seems unrelated to the actual posted example. | |
Jul 26, 2018 at 13:31 | comment | added | Ezoela Vacca | @Flater Yes that is correct. If the Enemy supertype accepts value 0-100 and so does the boss, that is OK. The boss only must not make it more strict, e.g. accepting only 10-50. | |
Jul 26, 2018 at 13:29 | comment | added | Ezoela Vacca | If you can, try to check C++ for Artists: The Art, Philosophy, and Science of Object-oriented Programming, page 490-494, available on Google books. The examples only deal with input parameter values. | |
Jul 26, 2018 at 13:29 | comment | added | Flater | @EzoelaVacca Examples are often oversimplified and not pedantically accurate as to real world applicability. The 0-10 value range is a specific value validation, it's more than just a changed behavior. For example, let's say damage is capped 0-100. But a boss does not take damage<5 because of armor. Therefore, when you enter 0-4 damage, the boss' life does not decrease. This is not a violation of LSP. It would be a violation of LSP if you made it impossible to enter these values (e.g. exception). But if you simply accept the value and don't actually do anything with it, that's not a violation. | |
Jul 26, 2018 at 13:24 | comment | added | Ezoela Vacca | @Flater I beg to differ, contracts in most C++ books dealing with LSP always include value, especially when dealing with input parameters (like I>0 && I<10). How else would you enforce parameter value requirement? | |
Jul 26, 2018 at 13:23 | comment | added | Flater | @EzoelaVacca: The contract should not specify values. It specifies behavior. "When life<=0" should not be part of the contract (at least not directly). "Dispose the enemy when it is dead" is the contract. The definition of dead is something that can vary based on subtype, but it will always be a binary result (is dead/is alive). The binary nature of death can be part of the contract (and most likely will be). | |
Jul 26, 2018 at 13:23 | comment | added | Ezoela Vacca | But as you implied, the expectation is the thing and the paper does mention that. I believe if the contract specifically mentioned the condition (life value) then this would be breaking it. | |
Jul 26, 2018 at 13:22 | comment | added | Ezoela Vacca | @Flater I do not mean the client code to decide anything, but if the contract says "When life<=0, enemy is destroyed" then the client code rightfully could expect the same from the Boss, right? | |
Jul 26, 2018 at 13:20 | comment | added | Flater |
@EzoelaVacca: The thing is, the client code expects that if life=<0, the enemy is destroyed. The issue here is the expectation. You can't at the same time give Enemy the responsibility to decide death and then have client code that independently decides whether death should have been applied or not. ("Do this if it needs to be done.""It didn't need to be done." "Well it should have!") The sole exception to this being unit tests, which do inherently contain fact-checking logic.
|
|
Jul 26, 2018 at 13:18 | comment | added | Ezoela Vacca | @Flater I do appreciate the discussion. The thing is, the client code expects that if life=<0, the enemy is destroyed. But that is not going to happen with the boss as the condition is more strict in that sense. This way, the child will not behave the way it parent would, if replaced. | |
Jul 26, 2018 at 13:15 | comment | added | Flater |
@EzoelaVacca: There is a massive difference between changing the input parameter's type from int to short (which does violate LSP) and simply doing something different based on a given valid input parameter (which does not violate LSP). For example, a normal enemy gets stunned when they receive damage > 15, whereas a boss only gets stunned when receiving damage > 30. That is not a violation of LSP. A violation of LSP would be Boss : Enemy implementing void Stun() { throw new Exception("Bosses cannot be stunned!"); as that does break the "enemy can be stunned" contract.
|
|
Jul 26, 2018 at 13:10 | comment | added | Ezoela Vacca | Anyway, as one of the answers suggest, I went through the original paper again and it does mention that you cannot decide on LSP violation based on the module itself, you need the contract specification about its behavior. | |
Jul 26, 2018 at 13:06 | comment | added | Ezoela Vacca | Actually I am not sure if you are correct because this way you could always argue that strengthening the condition is actually just changing the existing condition..and yet this is what LSP forbids. How does my example change from the textbook ones, such as: base class method accepts a parameter of type "int", child class "short" - LSP violated. But you could again argue that that we merely reduced the group of inputs. Main point is, LSP tells you not to make the condition stronger in subtypes, which I believe is my case. | |
Jul 26, 2018 at 10:56 | comment | added | Ewan | actually i do mention it, maybe you missed it | |
Jul 26, 2018 at 10:49 | comment | added | Ewan | hmm no, its very important that its Dispose and not FooMyBar. I suspected that you missed this | |
Jul 26, 2018 at 10:48 | comment | added | Flater |
@Ewan: I'm not saying that there are no issues with external consequences to disposing objects, but these issues do not matter for the question at hand. The method name could've been FooMyBar() and the topic wouldn't have been impacted in any way.
|
|
Jul 26, 2018 at 10:46 | history | edited | Flater | CC BY-SA 4.0 |
added 919 characters in body
|
Jul 26, 2018 at 10:45 | comment | added | Ewan | not a bad answer, if a bit long winded. but i think you miss the important factor of Dispose() having external consequences. Maybe add a section? | |
Jul 26, 2018 at 10:30 | history | edited | Flater | CC BY-SA 4.0 |
added 2899 characters in body
|
Jul 26, 2018 at 10:20 | history | undeleted | Flater | ||
Jul 26, 2018 at 10:18 | history | edited | Flater | CC BY-SA 4.0 |
added 2899 characters in body
|
Jul 26, 2018 at 9:53 | history | deleted | Flater | via Vote | |
Jul 26, 2018 at 9:53 | history | answered | Flater | CC BY-SA 4.0 |