5 ms·
In Go you might have an interface Rectangle": type Rectangle interface { SetHeight(int) SetWidth(int) GetHeight() int GetWi
by adamlett 10y ago
In Go you might have an interface Rectangle":
type Rectangle interface {
SetHeight(int)
SetWidth(int)
GetHeight() int
GetWidth() int
}
Then you could conceivably have a type Square which implements this interface.
The problem is, that clients that use the Rectangle interface make certain reasonable assumptions that turns out not to hold when the Rectangle in question is a Square. Case in point: If you call SetHeight(x) and SetWidth(y), you would assume that GetHeight() and GetWidth() return x and y. But that obviously violates the invariant of a Square if x and y are not equal to each other.
This problem is what LSP aims to address.
- masklinn 10y ago> The problem is, that clients that use the Rectangle interface make certain reasonable assumptions that turns out not to hold when the Rectangle in question is a Square. Case in point: If you call SetHeight(x) and SetWidth(y), you would assume that GetHeight() and GetWidth() return x and y. I don't know that that's a reasonable assumption, it doesn't work with any fixed-aspect-ratio rectangle (of which square is but one case). Basically, the issue here is that the interface is terrible and essentially mandates a single concrete type.
- adamlett 10y agoYou may quibble, but what I presented is more or less the textbook example that is often used to illustrate the LSP. I would argue that it is reasonable for any client of the Rectangle interface to make the assumption the setting the height does not affect the width and vice versa. The unreasonable part is providing such an interface to begin with. As a client of an interface, I shouldn't have to think carefully about whether or not certain assumptions I make, may be invalid in certain specific instances. If I make use of the Rectangle interface to model a resizable window in the GUI, I shouldn't have to worry how my GUI might behave if a Rectangle happens to be a Square. If your reaction is: “but you're doing it wrong!”, we are in agreement. But to reiterate, the thing I'm doing wrong is not to make (sometimes) invalid assumptions about the behavior of Rectangle, but to make it possible to make invalid assumptions by violating the LSP.
- masklinn 10y ago> As a client of an interface, I shouldn't have to think carefully about whether or not certain assumptions I make, may be invalid in certain specific instances. As a client of an interface you shouldn't make assumptions which are not guaranteed by the interface in the first place. > If I make use of the Rectangle interface to model a resizable window in the GUI, I shouldn't have to worry how my GUI might behave if a Rectangle happens to be a Square. The fixed aspect-ratio issue (square or not) actually applies very well to this scenario, video players will often have a fixed aspect-ratio (matching that of the video they're playing + some decoration), and while you can control the width or height: 1. there's no guarantee that they're independent 2. there's no guarantee that your setting a specific width or height will be acknowledged or unbounded That is not only setting the width may change the height (and vice-versa), there's no guarantee `r.SetWidth(x); x == r.GetWidth()` holds. And the interface certainly makes no such promise.
- adamlett 10y agoAs a client of an interface you shouldn't make assumptions which are not guaranteed by the interface in the first place. Sure. But as an author of an interface you should strive to make your interface easier to use correctly. It's easy to blame the programmer who makes the wrong assumptions about your code, but if you can make your interface in way that reduces the possibility of that happening, why wouldn't you? Large software platform vendors are generally more succesful if they instead of blaming application developpers for misusing the APIs, change the APIs so that they are easier to use correctly. The fixed aspect-ratio issue (square or not) actually applies very well to this scenario, video players will often have a fixed aspect-ratio (matching that of the video they're playing + some decoration), and while you can control the width or height [...] A video player getting getting its window locked to the aspect of the video it is playing may be the desired behaviour. Or not. In any case, it should be the video player that makes this decision, not whomever provides the video. Imagine you have an video library API that returns a Rectangle with the dimensions of the video, which previously returned a Rectangle proper (ie. one that allows changing width and height independently). In a recent update however, it changes its internal implementation, and while the signature of the API is unchanged (it still returns a Rectangle), it now returns a Square. This has the effect of changing the behaviour of your application though nothing in it has changed!
- catnaroek 10y agoThe real problem is that your interface doesn't actually describe a rectangle. It describes a mutable object whose current state is one rectangle, but whose state at a different point in time might be a completely different rectangle. Lest you think otherwise: removing the setters doesn't change anything. If you could define a type whose instances are actual rectangles, and then another type whose instances are rectangles whose width and height are equal (as a refinement of the original type of rectangles), it'd be clear as daylight that squares are rectangles.
- dragonwriter 10y agoThis is sort of a classic example (with the behavioral expectations of the interface that you have stated in the text, which are not expressable as a constraint in Go code -- or in most languages where the LSP is a concern -- but which often would be documented as an expectation of the parent class in a class-based inheritance system, and would usually be expected from the naming of the class/interface) of why mutability is problematic in terms of the LSP, as its not problematic at all if the signature is: type Rectangle interface { SetHeight(int) Rectangle SetWidth(int) Rectangle GetHeight() int GetWidth() int } Though, perhaps more than the LSP, this should be a textbook illustration of why representations of mutable objects in a program should not be named after entities in the domain whose essential identity is associated with one or more attributes that are mutable in the representation.
- adamlett 10y agoperhaps more than the LSP, this should be a textbook illustration of why representations of mutable objects in a program should not be named after entities in the domain whose essential identity is associated with one or more attributes that are mutable in the representation. Yes, in the example I gave, I agree. Certainly objects such as Rectangles and Squares should be modeled as immutable value objects. Immutability solves the problem completely. The example would perhaps be better if instead of Rectangles and Squares it talked about Windows and FixedAspectWindows.
- dragonwriter 10y ago> The example would perhaps be better if instead of Rectangles and Squares it talked about Windows and FixedAspectWindows. But, here, I think it should be quite clear that the solution is to make clear that independence of the dimensions on the two axes is not a guarantee provided by the Window interface or class. Interdependence of a particular form is guaranteed by FixedAspectWindow, and there might be a similar subtype of FlexibleAspectWindow that guarantees independence, but Window itself should not make any guarantee (and should, if necessary for clarity, expressly disclaim such a guarantee.) But one thing I think that it is worth highlighting is that the fact that some guarantees of a type are expressable and enforceable through code does not ensure that all are, and the LSP most critically applies to those that are not (since those that are expressed and guaranteed through code won't ever be a problem), but to those that are communicated through other mechanisms, including those communicated implicitly through naming.