Newsgroups: comp.lang.smalltalk
Path: utzoo!utgpu!cunews!knight
From: knight@mrco.carleton.ca (Alan Knight)
Subject: Re: Access methods - New feature ?
Message-ID: <1991Apr24.153744.24049@ccs.carleton.ca>
Sender: news@ccs.carleton.ca (news)
Organization: Carleton University, Ottawa, Canada
References: <VOSS.91Apr22183834@laslo.cs.uiuc.edu> <1991Apr23.010026.25098@ccs.carleton.ca> <VOSS.91Apr23194435@laslo.cs.uiuc.edu>
Date: Wed, 24 Apr 1991 15:37:44 GMT

In article <VOSS.91Apr23194435@laslo.cs.uiuc.edu> voss@cs.uiuc.edu (Bill Voss) writes:
>I wrote a few messages ago:
> >When I call another class's access methods, I almost always find I should have
> >opened another browser, and started adding methods to the other class instead.
>
>Alan Knight argues the following are counter examples
>>     Point     x, y
>>     Rectangle origin, corner, extent


Lots of stuff with some very good points omitted to try and reduce
length.  I'll summarize what I thought the good points were.
Summaries begin with --, quotes start each line with >, plain text is
my comments.

-- Lots of people access x and y values for points when they really
--shouldn't, either using point as an array of two values or to compute
--things that should have been made into methods for Point, with dist:
--being the example.
>	Obviously I should have been using "point1 dist: point2." instead.
>	However, NOT because dist: already exists.  I should have been using
>	dist: EVEN IF I NEEDED TO WRITE IT.  Unlike my code, the "dist:"
>	method was in the right place.  It needs to know about the internals
>	of a Point, so it is located INSIDE the class Point.

Absolutely.  A good example of when access methods are abused and code
duplicated for no good reason.


-- The class Point encourages programmers to break encapsulation.
>Exercise 2:  A new faster way of computing the distance between two
>	     points has been found.  Change all existing code in the
>	     Smalltalk-80 hierarchy to use the new method.

Yes, but this unlikely for this particular example.  Far more likely
is that you will decide you want to use a different metric for some of
your code, in which case isn't nearly as easy.  e.g. This UI code
doesn't really need precise distances, a Manhattan metric is fine to
test if the mouse is "close enough".  The method dist: should have
been used anyway, but as much to avoid duplication of code as to avoid
accessing representation.
   It also seems to me that the representation part is somewhat of a
side issue.  If points were represented in polar coordinates would x,
y, x:, y: suddenly become all right to use?  Does caching a previously
computed property inside an object make it wrong to access that
property?  It seems as if the issue is whether objects should return
properties of themselves, or if objects should always be dealt with as
an indivisible whole, where most messages don't return anything more
complex than a boolean.


>	I do NOT contend that it is ALWAYS wrong.
>	I think it must be judged on a case by case basis.
>	However, I do contend that it is USUALLY wrong.
>
>	I have not yet seen anyone post a good counter example.
>	They say this or that instance variable is "obviously"
>	an exception, but they don't show WHY they need to
>	access that particular variable.  Without knowing WHY
>	I can't tell wether they are correct or not.
>

OK, here are some examples, still using x and y.
Suppose that I have a set of points, and that I want to sort them.
There's no canonical sorting order for points, so I have to define
one.  I could define my sortBlock as 

   [ :point1 :point2 | point1 x <= point2 x]
OR [ :point1 :point2 | point1 y <= point2 y].

On the other hand, to avoid accessing the representation, I could
write
   [:point1 :point2 | point1 xIsLessThanOrEqualXFor: point2]


I contend that there is no additional encapsulation in the second
example, and no good reason to use it.  Of course for generality there
should really be a single sort block.
   [:point1 :point2 | (point1 componentInDirection: aVector) <=
	(point2 componentInDirection: aVector)]
which is no longer really an access method, but the efficiency gain is
significant enough that I believe a lot of people would write one of
the preceeding forms.


Another, and perhaps a better example is where one needs to know about
internals of more than one class at a time.  For example, suppose that
I have a geometric object and I need to do some mathematics on it (I'm
specifically looking at code for doing finite element analysis as I
write this, but think of it in general).  This mathematics probably
involves plugging x and y coordinates of some or all of the points
into some fairly complicated equations.
  Right now this code is in the finite element, which is (IMHO) where
it belongs, since it has to know a lot more about the details of the
finite element than about the particular points, but it does need to
know x's and y's.  I certainly don't think that having the methods

    Point>> findBasisFunctionsForEightNodeBrick: aBrick
            findBasisFunctionsForFourNodeQuad: aQuad
            findBasisFunctionsForTetrahedron: aTetrahedron

	etc. etc. is in any way a cleaner solution.


>	It is my contention that instead of using something like
>                        x1 <- point x.
>                        y1 <- point y.
>                        anObject myThingX: x1 Y: y1.
>        in those cases where you legitimately need to know x and y,
>        outside of Point, you should instead write a Point method:
>
>                doMyThingOn: anObject
>
>                        ^anObject myThingX: x Y: y.
>
>        I consider this CLEANER, more READABLE, more MAINTAINABLE, and
>        much harder to abuse than a straight ACCESS technique.
>
>        The fact that it usually involves both less programmer typing, 
>        and fewer runtime message sends is a nice bonus.

  Well, I have to admit that I don't consider this CLEANER, more
READABLE, or more MAINTAINABLE, although it might arguably be harder
to abuse.  Its main advantage seems to be that it gives you an idea of
who might be getting at the representation of point with a little more
detail than might be seen from senders of x or y.  It doesn't seem to
answer my previous example where I want x and y values for several
points without a great deal of convolution.
  I can see circumstances where this would be useful, but I think it
can wind up adding a lot of extraneous methods to Point.

  In summary, I don't think we disagree qualitatively.  Access to the
representation should be carefully watched, and you should always
think twice before doing it.  That doesn't mean that it's usually a
mistake, or that you should put methods in the "wrong" place to get
around it.  A method should be put in the class where it needs to know
the most about the internals, and knowledge of the internals of other
classes should be minimized, but cannot always be avoided.

-- 
--
  Alan Knight   knight@mrco.carleton.ca  +1 613 788 5783   Support
  Dept. of Mechanical and Aeronautical Engineering         the
  Carleton University, Ottawa, Ontario, Canada, K1S 5B6    LPF
