Another newbie question

Steven D'Aprano steve at REMOVETHIScyber.com.au
Sat Dec 10 08:52:42 EST 2005


On Sat, 10 Dec 2005 01:28:52 -0500, Mike Meyer wrote:

> Steven D'Aprano <steve at REMOVETHIScyber.com.au> writes:
>> On Thu, 08 Dec 2005 20:46:33 -0500, Mike Meyer wrote:
>>> Steven D'Aprano <steve at REMOVEMEcyber.com.au> writes:
>>>> Paul Rubin wrote:
>>>>> Steven D'Aprano <steve at REMOVETHIScyber.com.au> 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?
> 
> Given that we're talking about API design, then yes, you do. With only
> partial examples, you'll only get partial conclusions. 

I think you and I are coming at this problem from different directions.

To my mind, the Coordinate class was complete in potentia, and did not
need to be listed because I was not operating on a Coordinate instance as
a whole. If you go back and look at my example, I was explicit about
wanting to do something with a single ordinate, not a coordinate pair.
Listing the entire API for the Coordinate class would be a waste of time,
since I wasn't concerned about Coordinate.rotate(), Coordinate.reflect()
or any other method applying to a coordinate pair.


> In particular,
> you can get most of your meaningless methods out of a properly
> designed Coordinate API. For example, add/sub_x/y_ord can all be
> handled with move(delta_x = 0, delta_y = 0).

Here is my example again:

[quote]
Then, somewhere in my application, I need twice the 
value of the y ordinate. I would simply say:

value = 2*pt.y
[end quote]

I didn't say I wanted a coordinate pair where the y ordinate was double
that of the original coordinate pair. I wanted twice the y ordinate, which
is a single real number, not a coordinate pair.


[snip]

> And you've once again missed the point. The reason you don't
> manipulate the attributes directly is because it violates
> encapsulation, and tightens the coupling between your class and the
> classes it uses. It means you see the implementation details of the
> classes you are using, meaning that if that changes, your class has to
> be changed to match.

Yes. And this is a potential problem for some classes. The wise programmer
will recognise which classes have implementations likely to change, and
code defensively by using sufficient abstraction and encapsulation to
avoid later problems.

The not-so-wise programmer takes abstraction as an end itself, and
consequently spends more time and effort defending against events which
almost certainly will never happen than it would have taken to deal with
it if they did.

Do you lie awake at nights worrying that in Python 2.6 sys.stdout will be
renamed to sys.standard_output, and that it will no longer have a write()
method? According to the "law" of Demeter, you should, and the writers of
the sys module should have abstracted the fact that stdout is a file away
by providing a sys.write_to_stdout() function.

That is precisely the sort of behaviour which I maintain is unnecessary.



>> 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.
> 
> No, it requires you to actually *think* about your API, instead of
> just allowing every class to poke around inside your implementation.

But I *want* other classes to poke around inside my implementation.
That's a virtue, not a vice. My API says:

"In addition to the full set of methods which operate on the coordinate as
a whole, you can operate on the individual ordinates via instance.x and
instance.y which are floats."

Your API says:

"In addition to the full set of methods which operate on the coordinate as
a whole, you can operate on the individual ordinates via methods add_x,
add_y, mult_x, mult_y, sub_x, sub_y, rsub_x, rsub_y, div_x, div_y, rdiv_x,
rdiv_y, exp_x, exp_y, rexp_x, rexp_y...; the APIs of these methods are: ... "

My class is written, tested and complete before you've even decided on
your API. And you don't even really get the benefit of abstraction: I have
two public attributes (x and y) that I can't change without breaking other
people's code, you've got sixteen-plus methods that you can't change
without breaking other people's code.

(It goes without saying that these are in addition to the full set of
methods which operate on the coordinate as a whole -- our classes are
identical for those.)

The end result is that your code is *less* abstract than mine: your code
has to specify everything about ordinates: they can be added, they can be
subtracted, they can be multiplied, they can be printed, and so on. That's
far more concrete and far less abstract than mine, which simply says
ordinates are floats, and leave the implementation of floats up to Python.



-- 
Steven.




More information about the Python-list mailing list