On Thu, 08 Dec 2005 20:46:33 -0500, Mike Meyer wrote: > Steven D'Aprano <[EMAIL PROTECTED]> writes: >> Paul Rubin wrote: >>> Steven D'Aprano <[EMAIL PROTECTED]> writes: >>>>> Yes. Reaching through objects to do things is usually a bad idea. >>>>I don't necessarily disagree, but I don't understand why you say this. Why >>>>it is bad? >>> The traditional OOP spirit is to encapsulate the object's entire >>> behavior in the class definition. >> Uh huh. Say I have: >> >> class Coordinate: >> def __init__(self, x=0.0, y=0.0): >> self.x = x >> self.y = y >> >> pt = Coordinate(1.0, 2.5) >> Presumably then I also need add_y_ord, sub_y_ord, rsub_y_ord, >> div_y_ord, and so on for every method that floats understand, plus >> *another* set of methods that do the same thing for the x >> ordinate. And in every case, these Coordinate methods are trivial >> one-liners. >> Do people really do this? > > Yes, but usually with better API design. For instance, your Coordinate > class might have scale, translate, rotate and move methods.
Which I obviously left as exercises for the reader. Did I really need to specify the entire API for an example like this? > These are > still relatively simple, but they aren't one-liners. The import thing > is that these methods capture common, Coordinate-level operations and > bundle them up so that clients don't have to, for instance, do the > trig needed to rotate a point themselves. They can just use the rotate > method. Of course, if you have to do an operation on a *coordinate* then it makes perfect sense to create coordinate methods. I'm not disputing that. But in my example, I'm not doing things to a coordinate, I'm doing things to the entities which make up a coordinate -- the x and y ordinates. >> Yes, I could encapsulate the lot with a factory function that applied >> a specified operator to a specified attribute, and populate the class >> at runtime. But why would I want to? > > You don't. You're thinking about things at the wrong level. You don't > want to think about "things you do to a Coordinate's attribute". You > want to think about "things you do to a Coordinate". I've done my thinking about coordinates. That is why I wrote a Coordinate class. But sometimes you don't want to do an operation on a coordinate, you want to do something to the x or y ordinate alone. It is utter nonsense to suggest that you should abstract coordinates to the point that you no longer know -- or pretend that you don't -- that a coordinate is a pair of ordinates. What, we're supposed to guard against the possibility that coordinates might be implemented by a B-tree or something? (Yes, I'm aware there are 3D coordinates, or even n-dimensional ones, and all sorts of special purpose coordinates with, e.g. ordinates limited to integral values. I'm not writing a general anything-coordinate class, I'm writing a simple 2D coordinate pair class.) >> Now, as I see it, the whole point of encapsulation is that you *don't* >> need to fill your class definition with meaningless helper functions. > > Correct. That's where you went wrong - your methods were essentially > meaningless. They just manipulated the attributes, not the Coordinate. > Your methods should be meaningful for the object, not just the > attributes. *Exactly* my point -- and demonstrating that you've missed that point. Writing special purpose methods to manipulate object attributes when you can just as easily manipulate the object attributes is a bad idea. Methods should be meaningful for the object. According to the Law of Demeter (more of a guideline really), each level should only talk to the immediate level next to it. Fine: I have a name "pt", which is bound to a Coordinate object -- so "pt" can call Coordinate methods. But it shouldn't call float methods on the attributes of that Coordinate object, because those float methods are two levels away. The bad side of the Guideline of Demeter is that following it requires you to fill your class with trivial, unnecessary getter and setter methods, plus methods for arithmetic operations, and so on. Or just say No to the "law" of Demeter. As a guideline, to make you think about what your doing ("am I doing too much work? should my class implement a helper function for this common task?") it is perfectly fine. But when you find yourself writing trivial methods that do nothing but call methods on a attribute, you are another victim of a bad law. >> If an attribute of a instance is a float, you can just call float >> methods on the attribute and it should work. If the attribute is a >> list, list methods will work. If the attribute is an instance of a >> custom class, the same general technique will still work. > > So, if we expand your Coordinate class to have attriutes r and theta > (the same coordinate expressed in polar form), which are also floats, > does it make sense to write: pt.r *= 2? If I expand the Coordinate class to allow both polar coordinates and Cartesian coordinates, I need some mechanism for keeping the two views in sync. If I can do that (say, with properties) then sure it makes sense to double the length of the coordinate vector. If I *can't* keep the two views in sync, then I have some choices to make. For instance, I might have a Cartesian class and a Polar class, with methods to convert from one to the other, and give up on the desire for one object to encapsulate both. My Coordinate class doesn't encapsulate writing a point in English "one point two, three point four five" either -- you shouldn't expect a single class to encapsulate every imaginable representation of that class ("but what if I want to write my binary tree in Morse code?"). -- Steven. -- http://mail.python.org/mailman/listinfo/python-list