Skip to main content
24 events
when toggle format what by license comment
Jul 28, 2019 at 19:10 comment added Phoshi @Flater "Liskov was describing something not useful for programming in business" is a criticism of LSP, but does not cause the definition to change. IMO it is entirely fair to say that violating LSP is desirable for your use case... but it doesn't change what it is.
Jul 28, 2019 at 18:10 comment added Flater @Phoshi that sum(2, 3) = 5 is a property provable, and thus any subtype must also have that property provable. If every derived method was required to do or return the exact same thing, there would never be a point to override a method as the only purpose of overriding is to change its (base) method body. The issue with Liskov's examples is that she uses clearly defined mathematical constructs (a sum is rigidly defined), but in business logic such rigidity in definitions is more often absent than present; the existence of a derivation tends to imply that different behavior is needed.
Jul 24, 2019 at 22:39 comment added Phoshi @Flater The wikipedia page has some fairly decent additional definitions--note especially the links off to design-by-contract concepts and references to the definitions of behavioural subtyping
Jul 24, 2019 at 22:37 comment added Phoshi @Flater To map our example precisely onto the author of LSP's definition, let x be an instance of Summer and y be an instance of WrongSummer and Φ(f) be the property f.sum(2, 3) == 5. Φ(x) is true, because Summer.sum(2, 3) == 5. Φ(y) is false, because WrongSummer.sum(2, 3) != 5. This isn't about documentation or interfaces or any of that, it's more basic and restrictive than that. LSP is about behaviour, and behaviour covers everything--technically right down to bugs in the original implementation.
Jul 24, 2019 at 22:33 comment added Phoshi @Flater I think it might be useful to go back to Barbara Liskov's own definition here, because I don't think either of us will word it better: "Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.". I'm not sure what property you are describing, but it is not the strong behavioural subtyping that Barbara defines for the LSP. For our Summer, that sum(2, 3) = 5 is a property provable, and thus any subtype must also have that property provable. IMO her words are very clear here, LSP is extremely restrictive.
Jul 23, 2019 at 18:20 comment added Flater @Phoshi: LSP is not a catchall principle for all inheritance abuse. It very specifically focuses on the fact that when a Foo class implements a method that should never be called (or whose usage changes to a point of serving an entirely different purpose), then the interface/base class that requires its usage should not be used as part of the Foo class. In other words, never implement required methods that you then expressly want to never be used (or use in a way they were never intended to be used).
Jul 23, 2019 at 18:15 comment added Flater @Phoshi: [..] If that class was called Flooper because it floops (= custom calculation that I've invented), your point would no longer stand since you don't know what flooping is and can't inherently label the output as wrong or right. That proves the point that you're focusing on a contextual/documentative issue, not a technical one.
Jul 23, 2019 at 18:15 comment added Flater @Phoshi: LSP has nothing to do with the correctness of the value that is returned. Having a derived method that returns a fixed number is perfectly fine if returning the fixed number is correct for the class in which it is implemented (mocking is the prime example here). What you're focusing on is the context (i.e. "this class says it sums two numbers") that is wrong compared to the actual behavior (always returning 4 and ignoring the input) which is not the focus of LSP. Your issue is of a documentative nature. [..]
Jul 23, 2019 at 17:07 comment added Phoshi @Flater so if you have WrongSummer which always returns 4, swapping the types out changes the desirable properties of your program, i.e., it no longer works. Exactly the same with throwing something unexpected, your program no longer works, and thus you have violated a strong behavioural subtyping constraint by changing the behaviour in your subtype.
Jul 23, 2019 at 17:05 comment added Phoshi @Flater IMO that's a very restrictive definition of LSP. Ignoring languages like java where it is possible (caveats apply) to define on a technical level which exceptions can be thrown, LSP must go deeper than merely what types are part of the signature. That part is checked by your compiler most of the time--LSP is about strong behavioural subtyping. If I have some type Summer { int sum(int a, int b) } then what LSP is stating is you should be able to swap this to any subtype of Summer and "without altering any of the desirable properties of the program".
Jul 23, 2019 at 13:39 comment added Flater @Phoshi: What you're mentioning is a violation of your own documentation (which would in that case contradict the actual behavior); it is not a LSP violation. Documentation that contradicts the actual behavior is always an issue (as it invalidates the documentation), regardless of what it's about. That's not an LSP issue. There is no way to define (on a technical level) what exception can possibly be thrown from a given method. Listing the exceptions (and the circumstances of them being thrown) is purely documentative.
May 9, 2018 at 14:44 comment added Phoshi @Flater I think there's more nuance to it than that. If I have some IFoo.Bar(int frob) which is not defined to ever throw, then throwing is an LSP violation. If this means your implementation of IFoo cannot adhere to the contract, then the contract was poorly designed, but that doesn't make violating it any less an LSP violation. More reasonably, if it's defined to only throw FooException or ArgumentException, then throwing another exception type is an LSP violation. Exceptions are just as much a part of the interface as everything else and are violated in the same way.
May 9, 2018 at 12:43 comment added chrisyue @Flater As Ewan has answered the second question in details and the solution did enlighten me, so I decide to mark Ewan's answer as the right one. Still, thanks for the excellent answer, it helps me a lot and surely it will help more :)
May 9, 2018 at 11:46 comment added cHao @Flater: Inheritance is what lets you declare a Derived as a Base. Polymorphism is what lets you call ohj.do_something() and have obj (or rather, in most languages, its class) decide what exactly that means. Strict static typing has shackled the two together for so long that the confusion is understandable. But only when you let the object itself direct things, and aren't concerned with what specific type you're given, are you using polymorphism.
May 9, 2018 at 7:23 comment added Flater @cHao: I consider it an abuse of polymorphism as (in my earlier example) the variable is needlessly declared as type Base, which you're only allowed to do because of polymorphism. In pretty much every case I've seen, the developer has a valid reason to want to know the specific type later on; but he has already shot himself in the foot by unnecessarily downcasting the type at an earlier stage. To me, that means the issue is located in the earlier stage.
May 8, 2018 at 19:31 comment added cHao @Flater: Type checking period is abuse. Not of polymorphism, but of inheritance. It's actually a disuse of polymorphism, as you're no longer relying on your object to do the right thing unless it's of some specific type. Absent a type declaration, I should be able to pass a Url in place of a string and have it work.
May 8, 2018 at 11:59 comment added Flater @chrisyue: Those are valid concerns (I don't like untyped parameters for exactly this reason), but that's not really what LSP is about. Having to type check is a logical consequence of not enforcing a type to begin with. If you don't like type checking, then enforce a type. Problem solved. Type checking of typed variables (e.g. Base b = new Derived(); if(b is Derived) { HandleDerived(d); }) is more often than not an abuse of polymorphism; and I suggest avoiding this altogether to prevent future code smells and LSP violations.
May 8, 2018 at 11:53 comment added chrisyue I think the origin example also break the LSP because the original contract TransformerInterface::transform is considered to handle "everything", like PHP's var_dump function. However the TagTransformer::transform only can handle "string". which should be more appropriate as TagTransformer::transform(string $origin). no matter there's typehint or not, I always fell it's a code smell of type checking for parameters, no matter it's "typeof" or "is_xxx"
May 8, 2018 at 11:16 comment added Flater @Phoshi: Maybe a better reply after re-reading your comment: Regardless of the contract definition, if no meaningful result can be achieved; then an exception is the only reasonable outcome. LSP or not, if there's no other option than throwing an exception, throwing an exception can't be wrong or in violation of anything.
May 8, 2018 at 8:57 history edited Flater CC BY-SA 4.0
edited body
May 8, 2018 at 8:54 comment added Flater @Phoshi: I was already drafting an update to the updated example (see my answer now). The difference is that in OP's initial example, the exception was related to the language used (which allows typeless parameters - thus opening the door to unexpected types being passed - thus making it acceptable to throw on unusable types, in my opinion). However, in the revised example, he's now working with a typed parameter (which in and of itself displays inheritance on top of that), which does significantly change the violation here. I hope you agree with my updated answer.
May 8, 2018 at 8:52 history edited Flater CC BY-SA 4.0
added 1427 characters in body
May 8, 2018 at 8:49 comment added Phoshi I think both your and the question's examples are LSP violations if it is not part of the interface contract that invalid arguments throw in that way. If eat is defined as "Instance eats the specified food or throws InvalidArgumentException if it's incompatible" then we're good. If it's merely "Instance eats the specified food" then we're not good, but also the Animal interface was poorly designed.
May 8, 2018 at 8:41 history answered Flater CC BY-SA 4.0