9 ms·
A Square Is Not A Rectangle
- raganwald 17y agoI have a simpler dictum: Square was-a Rectangle. In other words, we wish to implement a square using the rectangle class, but we do not acknowledge that a square is a rectangle at all. Implementation inheritance should sometimes be independent of interface inheritance. If I was forced to use interface inheritance for this, I would say that both squares and rectangles are abstract quadrangles, but rectangles allow individual sides to be set and squares do not.
- yannis 17y agoThe article is good at pointing the logic faults. As you correctly say, the abstraction is wrong. If you were abstracting classes for a graphics program the abstraction could even go one level higher in abstracting p o l y g o n s, rather than rectangles and squares. You can even have crossed rectangles (http://en.wikipedia.org/wiki/Rectangle http://en.wikipedia.org/wiki/Rectangle). Using such an abstraction IMHO any polygon shape would consist of line objects which would consist of points (x,y and even z)! OOP abstractions can sometimes be masochistic! ;)
- deleted 17y ago[deleted]
- toadpipe 17y ago> OOP abstractions can sometimes be masochistic! You think? The article is a train wreck of analysis piled on the train wreck of "OO Design", which is itself a train wreck of an attempt to promote reuse by modeling code with data types, which is itself a train wreck of an attempt to imagine what hypothetical programmers in some hypothetical universe might find "useful" as masturbatory aids, hypothetically speaking. (This is not actually true - OO[D] is really an attempt to commoditize programming.) Most of the comments here are just dumping more trains on the pile. The abstraction shitheap is so high at this point that we can barely even remember that we are supposed to be solving some kind of actual problem. The best part of the article is when the author acknowledges that the code might not actually need to be thread safe and so avoids wasting his time with that particular assumption. There is nothing else to be salvaged. A square is not a rectangle? Goodbye mathematics - what is the point of a type system again? More to the point: what planet are the users of this API from? Obviously not this one, and just as obviously no one knows or cares. Not ONCE does the author stop to wonder what services the actual users actually need; it's only about what this guy thinks MIGHT comply with some random "design" rules. One wonders if the users even exist as anything other than just another abstraction, and wondering about what problems those users might actually be trying to solve is right out. Object Oriented Design can be seen as a case of reusing a cliche and butchering it at the same time, with the cliche being the shooting of ones self in the foot, and the butchery being the placement of said foot squarely upon the face of beauty immediately before pulling the trigger.
- barrkel 17y agoI think you're going way overboard with your cynicism and criticism of OOP - cynicism bordering on paranoia. I've read your other comment, alleging that OOP "was pushed by manager types who just wanted as many reports as possible". The dominant paradigm before OOP was procedural programming, and it had (and has) real problems: tendency towards storing data in globals, lack of a machine-checked mechanisms for subtyping, less discoverable APIs (often a namespacing issue), and difficulty in sharing code when representing abstractions where many kinds of values need to be treated very similarly but with little differences, GUI widgets in particular. OOP is unmistakably a better approach for a large class of problems, primarily because it encodes good procedural practice into well-defined concepts that are checked by the machine, either at compile time, run time, or both. Functional programming has a different kind of benefit: whereas OOP helped modelling values, FP helps modelling algorithms. Because strict FP demands pure functions and immutable data, you can reason about complex functions and expressions knowing that there are no side-effects that can bite you. The benefits of FP over OOP aren't as clear as OOP over procedural programming, as taking the strict approach makes state management problematic and limits freedom in data structure design and ultimately algorithmic complexity, but the benefits are very clear in certain areas.
- toadpipe 17y agoI note that every advantage of OOP you cite is a modularization feature (even subtyping, since OOP basically conflates modules with types). Clearly you are not entertained by rants based on my experiences, so maybe I should just cut 'n paste pg: "Object-oriented programming is exciting if you have a statically-typed language without lexical closures or macros. To some degree, it offers a way around these limitations. (See Greenspun's Tenth Rule.) Object-oriented programming is popular in big companies, because it suits the way they write software. At big companies, software tends to be written by large (and frequently changing) teams of mediocre programmers. Object-oriented programming imposes a discipline on these programmers that prevents any one of them from doing too much damage. The price is that the resulting code is bloated with protocols and full of duplication. This is not too high a price for big companies, because their software is probably going to be bloated and full of duplication anyway. Object-oriented programming generates a lot of what looks like work. Back in the days of fanfold, there was a type of programmer who would only put five or ten lines of code on a page, preceded by twenty lines of elaborately formatted comments. Object-oriented programming is like crack for these people: it lets you incorporate all this scaffolding right into your source code. Something that a Lisp hacker might handle by pushing a symbol onto a list becomes a whole file of classes and methods. So it is a good tool if you want to convince yourself, or someone else, that you are doing a lot of work." http://www.paulgraham.com/noop.html http://www.paulgraham.com/noop.html
- silentbicycle 17y agoComputer programming is young enough as a field that the mainstream hasn't realized it isn't possible to fit everything into one unified taxonomy. (Biologists and librarians have both spent over a century trying, and they have a lot of insight on the matter.) Putting types into a hierarchy is a useful abstraction sometimes, but there's not some Grand Truth in it -- often, attempts at cramming disparate elements into a hierarchy just create extra complexity. Most systems will keep trying to turn back into graphs unless you anchor the hierarchy in a specific context (what does 'a IS-A b' mean, really?), and the context tends to shift with the problem definition. Worse still, it's the sort of complexity that looks like work, even though it's usually more like flailing in quicksand. I think not having to deal with class hierarchies is a big reason why some people find dynamically-typed languages so freeing. I'm still kind of new to Erlang, but I think its push towards modeling things via a graph of communicating processes, with a relatively flat type structure, is worth considering as an alternative. It explicitly focuses on the message protocol between actors, rather than a tree of subtypes. Granted, I haven't worked a large enough project to see it break down and get ugly yet. (C++ style OO sounds good on paper, too.) Regardless, it isn't classes per se that lead to OO's problems, but the tendency to overuse inheritance. Among statically-typed languages, Haskell's typeclasses seem like another good solution - they break the hierarchy of inheritance up into composition of properties. Also, the late-binding in OOP means that any class can potentially be responsible for maintaining its invariants even after any of its methods has been overridden by any possible future subclasses. That's not necessarily true even with dynamically-typed languages.
- BigZaphod 17y agoThat's kind of how I think of the goal of finding a grand unified theory in physics. It seems to me that if such a thing is ever found, it means there is some unifying and "correct" organization to the universe and that'd revolutionize a lot of stuff when the same ideas and structures are applied to all other fields. It'd be a huge win if this worked out - and not just for physics. In my warped and uninformed mind, the grand unified theory, the perfect programming model, P=NP, and countless other such edges of theory are all different facets of the same entity that we have yet to identify - assuming it even exists in an identifiable way in our reality.
- jhancock 17y agoI understand the author is trying to show pitfalls of OO design. But the square/rectangle example doesn't do it for me. 1 - "There are actually several problems here. Thread safety is one, but let’s assume the class doesn’t need to be thread-safe." The Rectangle implementation has a thread issue without regard to the Square subclass: public double getPerimeter() { return 2*width + 2*height; } 2 - "In object oriented programming, it is necessary that a subclass be able to fulfill the contract of its superclass. In this case, that means the square has to respond to setHeight() and setWidth() calls. However doing so enables the square to violate the nature of a square. A square cannot stand in for a rectangle. You can try to work around this by overriding the setHeight() and setWidth() methods. For example, one might call the other:... However this is fundamentally unsatisfying because there is no reasonable expectation that calling one of setHeight() on a Rectangle object will also invoke the setWidth() method or vice versa." This is where things unwind. It is perfectly expected that calling setHeight() on a Square will also change its width. 3 - "We could instead just forbid setHeight() and setWidth() completely by throwing UnsupportedOperationException" Yep, you could do that and it does show you are doing more work in working around using Square as a subclass of Rectangle. So either don't subclass it or accept the fact that its perfectly normal that if you have a Square, changing either its width or height will effect the other. The author is trying too hard here. Its decent writing and a good attempt to show how subclassing can lead to unintended consequences. But this is not a great example.
- txxxxd 17y ago"It is perfectly expected that calling setHeight() on a Square will also change its width." If you're writing code that operates on Rectangles you wouldn't expect setHeight to have the side-effect of changing the width too. It's only "perfectly expected" if you know about the implementation details of Square, which may not even have existed until after a lot of Rectangle code was already in place.
- jhancock 17y agoyour correct. And this is why the example is not good. Your saying someone might not expect the "side effect" of width changing when height changes. But this is a semantic setup. People do know how a Square is supposed to behave. One of the characteristics of a "first class object" is that there is common agreement on behavior. If we don't have this shared agreement, lots of things about OO design fall apart.
- noblethrasher 17y agoStudying functional programming can really illumniate certain concepts in object oriented programming. The solution is a sum type which you can simulate in OOP using an abstract base class (in C# parlance) and a set of sibling classes that derive from it. So, as other have mentioned, you could have an abstract Polygon (or quadralateral) base class
- davidmathers 17y agoAka: http://en.wikipedia.org/wiki/Circle-ellipse_problem http://en.wikipedia.org/wiki/Circle-ellipse_problem
- DannoHung 17y agoThis smells like a job for Type Classes.
- deleted 17y ago[deleted]
- spooneybarger 17y agoa square is indeed not a rectangle therefore i see this as the only flaw that needs to be pointed out: public class Square extends Rectangle inheritance for the sake of implementation is bad OO design.
- raganwald 17y agoIt is an old concept that got ignored somewhere along the road to java. Inheritance for the sake of implementation is called WAS_A. In C++, this is what private inheritance does. In languages like Java and Ruby, WAS-A is often implemented using delegation, so that a square HAS-A rectangle.
- KevinMS 17y agoProblem is from historical conventions of what a square and rectangle are. A square is not really a thing, its a description of a thing. In this case, proportions of the sides of a shape. So when you translate this into object oriented language, you are subclassing a concept with one of its potential variations. It would be like making a class called MusicalNote and then a subclass named MusicalNoteC, even though MusicalNote already has it covered. So "square" shouldn't be a subclass because its just a proportion of the data within "rectangle", so more appriopriately, 'square' would be a method of Rectangle since it deals with internal data.
- igrekel 17y agoFunny I remember seeing a library (used somewhere internally) where the Rectangle inherited from square. It was a surprising discovery when reading the codebase but the implementation as odd tough.
- fsniper 17y agoIf you believe OOP is "making physical objects' memory maps", instead of "related data put together" then yes there are some problems on some conditions. But this is caused by unrelated physical mapping need. If you can let go about this belief and think a bit lower level, OOP is a good enough methodology for thinking "like" objects. But it's not perfect. I don't know if this mapping need is good for software engineering.
- gcanyon 17y agoI had an instructor once who started a lecture with this example, and then argued that rectangle should be a subclass of square.
- flatline 17y agoOO is an incredibly powerful tool but is unnecessary for so many tasks. The contrived examples of many books show beautifully, as does this example, simple inheritance hierarchies that get at the gist of it but are not really an appropriate use of the technology. Everything does not need to be modeled as an object, oftentimes with only one or two methods, no internal state, etc. Some of the newer multi-paradigm languages (F# and Scala come to mind at first glance) seem poised to rescue us from improper use of OOP when you are stuck in an OO environment with a non-OO pattern or problem to solve. You get the OO stuff when you need it, with a more flexible syntax, without being strapped into a design straightjacket.
- r00k 17y agoMake sure to check out Uncle Bob's comment (it's first after the post.)
- ubernostrum 17y agoSigh. Those who do not read Quine are doomed to reproduce his results, as I remind people every time Uncle Bob or someone else of the cabal starts going off on this problem: http://www.reddit.com/r/programming/comments/7zy40/writing_unit_tests_is_reinventing_functional/c07w5bx http://www.reddit.com/r/programming/comments/7zy40/writing_u...
- davidmathers 17y agoI'll see your Quine and raise you a Bolzano: http://plato.stanford.edu/entries/bolzano/ http://plato.stanford.edu/entries/bolzano/ http://plato.stanford.edu/entries/bolzano-logic/ http://plato.stanford.edu/entries/bolzano-logic/
- graywh 17y agoWhat about the rhombus?!
- akeefer 17y agoBeyond just the obvious "this is a bad way to design things" message, another good takeaway is just the fact that most OO-design tutorials/discussions/books use the worst possible examples that teach people bad habits. When done well, the useful part of OO design is encapsulation and data-hiding, not inheritance, but everyone gets hung up on inheritance because it's easy to cobble together "is-a" examples from real-world-like things, which just trains people to abuse the model. In the real world outside OO-design books and freshman CS classes people don't have separate classes for different shapes, they just have "Polygon" or whatever the graphics buffer requires. And no one has separate subtypes of "AbstractVehicle" for "PassengerCar" and "Motorcycle" and "CommercialTruck," they just have a "Vehicle" class with a bunch of data on it and rely on the client code knowing what to do with that data.
- iron_ball 17y agoThis is why Eric Evans' "Domain-Driven Design" should be on every programmer's reading list. He applies object-oriented principles (though not exclusively) to real-world-sized problems, frequently pointing out the traps inherent in the enterprisey languages most big systems will use.
- Tichy 17y ago"Another is that it’s possible to give the sides negative lengths." Is that really a problem that needs fixing? Also the with/height problem - one could argue the fault would lie with the code that tries to do both... A while ago I had a realization as to what defines the Java developer mindset: it is fear driven development (FDD). (Taking Java as an example because it is the only world I know a little bit better). Maybe sometimes FDD is being called for, or maybe it really is the superior development style. Personally, I am quite sick of it, and it definitely makes it harder to get things done in my opinion.
- tetha 17y agoWell, the problem in my opinion is, that a square is a rectangle, indeed. However, this is not square is-a rectangle;, but rather: square is-a rectangle with rectangle.height == rectangle.width; This is overall something I have been pondering (and recently planning and implementing) to extend the standard object orientation: adjectives (opposed to 'nouns' (objects) and 'verbs' (methods)). An adjective would simply describe a noun furhter and simplify a dispatch inside a method depending on the state of an object. Some pseudosyntax from the current state of ideas would go like this: adjective_group rectangle_type(R) { if(R.width == R.height) { return square; } else if(R.width * 4 = R.height * 3) { return widescreen; } } draw(square rectangle R) { // specialized draw for squares } draw(widescreen rectangle R) { // specialized draw for widescreen rectangles } draw(rectangle R) { // general drawing method for rectangles } This goes pretty deep if one starts to think about it: * Buffers: empty, full, something in between * Abstract syntax trees: well-typed, ill-typed * Accounts: banned, active, moderator, admin Basically, it simplifies the pattern method(object) { if (predicate_1(object.state)) { // stuff } else if(predicate_2(object.state)) { // other stuff } else { // default stuff } } I am just currently working on a dispatch mechanism which is at least a bit intuitive :)
- silentbicycle 17y agoHave you ever used a language that makes heavy use of pattern matching? Prolog, in particular, or to a lesser extent (due to increasing amounts of distraction by other novel features) Erlang, OCaml/SML, or Haskell. It sounds like you're essentially describing functional dispatch based on structural attributes or current characteristics, rather than a (generally fixed) object identity.
- tetha 17y agoyes, this is where a large part of the idea comes from.
- barrkel 17y agoThis just comes down to the difference between identity that changes with mutation, and identity which is constant with mutation. All squares are rectangles. Some rectangles are squares. If the object used to represent squares and rectangles is mutable in width and height, then its squareness cannot be immutably associated with its identity, and thus cannot be encoded as a type, since objects in static languages have immutably associated types. There's some deep point about naming objects after their attributes, without paying attention to whether the attributes may change. Experienced programmers know this well, but laypeople don't need to make the distinction often as their language usage is usually only temporary, not needing to account for changes over time. It's the statement that must always be true, when all the right qualifiers and conditions have been included, that's hard to make.
- joe_the_user 17y agoNo, there is no easy answer to this problem in a way that jives with all doctrines and also works in practice. OO is ambiguous as to whether a "subclass" of an object is specialization of the parent object or a generalization. In practice, most subclasses are a generalizations and specializations introduce all sorts of problems, especially enforcing constraints. This naturally comes down to OO trying to shoe-horn (at least) four related-but-not-necessarily-identifical activities together: code/data encapsulation, program design, code-reuse, and modeling (the translation of real-world objects-and-categories to code). The net result is that OO languages, tools and methods can be useful for writing large program and can give you effects that are better than the alternatives but that the pronouncement of OO gurus wind-up sounding idiotic and following the book to the letter can create situations that negate the particular benefits of OO.
- barrkel 17y agoBy Liskov substitution principle, or with the set view of types, subclasses are always specializations / subsets, unless you're introducing some kind of home-baked type system built on top of the existing type system. I would classify the parts of OO differently. I think your second and fourth activities are the same, but one models abstract concepts, and the other models concepts of concrete things. I wouldn't bring in code reuse at this level; every practical programming paradigm tries to include code reuse. It's the mechanism that's a distinguishing feature, and the mechanism OO uses is a combination of subtype polymorphism and dynamic dispatch. It's a baked-in version of the function table / union|variant record idiom from procedural programming, and it has a transposed expression in sum types and pattern matching in functional programming: OO limits the addition of new (virtual) methods (on the base) but accepts the creation of new types (even at runtime), while FP limits the creation of new types (in most implementations I've seen) but accepts the addition of new methods (which costs some in discoverability in modern IDEs, and has namespace implications).
- BillRossum 17y agoNot a link.
- tigerthink 17y agoMirrored from Google's cache: http://jottit.com/qykre/ http://jottit.com/qykre/
- apotheon 17y ago403 Forbidden Damn. Did I miss it?
- apotheon 17y agoOkay, so I came back finally and checked again to see if I could get in. My thought, now, is that a functional approach to making square a "subclass" (by stretching the term "subclass" out of the confines of its traditional Java-style OOP usage) of rectangle. (define (square s) (rectangle s s)) Simple. Yes?
- scharan 17y agoI really like this comment from Uncle Bob: Inheritance is not ISA. It is a redeclaration of variables and functions in a sub-scope! http://cafe.elharo.com/programming/a-square-is-not-a-rectangle/#comment-436689 http://cafe.elharo.com/programming/a-square-is-not-a-rectang...
- JulianMorrison 17y agoMy Haskell way of thinking of it would be that Square and Rectangle are distinct (non-inheriting) data types, Square and Rectangle are both instances of the type-class Rectangular (which inherits and provides default functions for Closed and Polygonal), Square has a transformation into Rectangle, and Rectangle has a transformation into Maybe Square.