Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Perl OO and accessors

by dragonchild (Archbishop)
on Nov 28, 2005 at 20:28 UTC ( [id://512359]=perlmeditation: print w/replies, xml ) Need Help??

(This is a reply to The Accessor Heresy. I'm doing it as its own meditation because that thread is getting full, plus I feel that this deserves a bit more attention.)

There's a lot of talk about accessors in Perl's OO where there isn't in the OO for other languages1, and for good reason. But, it's not the reason most people think. When you're introduced to OO through Perl, you write accessors. But, that's not because you need to in order to write OO - it's to reduce the possibility of typos when dealing with hash-based objects.

When designing a class, one should be considering how the client will want to use it. In OO, interface is king. Let's take a circle, for example. Disregarding the thorny nest that is a Shapes class hierarchy, we know we want our circle to be able to take a value when it's being built. (We'll get to what that value is in a sec.) We also want to be able to ask it what its radius and area both are. So far, so good. So, we have the following:

  • new( Float ) -> Circle
  • radius() -> Float
  • area() -> Float

Now, when building our circle, we may want to specify the radius or the area. So, instead of a single new(), we may need two constructors - new_from_radius() and new_from_area().

So far, so good. We have a circle object that meets every one of our requirements. Now, let's say we receive a requirement that says "We need to be able to change the circle's size." Ok ... the first idea that comes to mind is "Let's expose the attributes we're using and let the client change them as needed." And, that's the worst idea. The better idea is to provide methods that allow the client to adjust the size. So, maybe something like:

  • adjust_radius( Float ) -> void
  • adjust_area( Float ) -> void
  • set_radius( Float ) -> void
  • set_area( Float ) -> void

"Hah! You're using accessors now!" Actually, I'm not. An accessor exposes an attribute. I haven't ever mentioned implementation. All I've discussed is the interface, which can be satisfied by any number of implementations. As far as we know, the implementation could be a center point and any point on the circle itself. Or, it could be any three points on the circle. Neither of those implementations have a "radius" or an "area" attribute.

  1. I'm referring to real OO languages, like Smalltalk and Ruby. I'm going to bypass C++ and Java because they're mostly OO, not really OO.

My criteria for good software:
  1. Does it work?
  2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Replies are listed 'Best First'.
Re: Perl OO and accessors
by chromatic (Archbishop) on Nov 28, 2005 at 21:30 UTC
    When you're introduced to OO through Perl, you write accessors. But, that's not because you need to in order to write OO - it's to reduce the possibility of typos when dealing with hash-based objects.

    More importantly, it's so you can maintain the same interface without dictating any internal implementations of any other class that has to offer the same interface. My article in the December 2005 The Perl Review explains more.

Re: Perl OO and accessors
by itub (Priest) on Nov 28, 2005 at 21:14 UTC
    "Hah! You're using accessors now!" Actually, I'm not. An accessor exposes an attribute.

    What do you mean by "exposing"? So, if you have an implementation that uses the radius as the internal attribute and add a "set_radius" method that just does something like $self->{radius} = shift, you are using an Evil Accessor, but if you have the area as the internal attribute and add a set_radius that does $self->{area} = $PI * shift**2, you have an smart and innocent method? The interface is identical and the user shouldn't know the difference, therefore there's no way of telling, from the outside, whether a method is an accessor or not. Which leads me to the conclusion that all the ranting I hear about accessor methods is nonsense.

    Now, what is a true problem is when accessor methods let the user put the object in an inconsistent state. But I call that a plain bug, not accessor evil. For example, let's say that the object stores both the radius and the area (to save recomputing the area, which as we know, is extremely expensive ;-). Then it would be bad to have a set_radius method that only does $self->{radius} = shift, because the area would be left with the wrong value. The set_radius method should be ammended to recompute the area as well.

      Which leads me to the conclusion that all the ranting I hear about accessor methods is nonsense.

      I think that's a good conclusion.

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: Perl OO and accessors
by xdg (Monsignor) on Nov 29, 2005 at 05:36 UTC

    I haven't fully gone through the other thread, but I think this issue -- at least for Perl -- really needs to be be distinguished between accessors and mutators used within the class package and those used outside the class package.

    Within the class package, accessors and mutators are heavily used for typo avoidance -- or rather, compile-time run-time typo checking. That's a straightforward tradeoff of safety for speed and fair enough. (And yes, inside-out objects address some of that, with yet other tradeoffs.)

    Outside the class package, the issue of whether accessors and mutators violate encapsulation, etc., is what becomes the religious war. The "problem" for Perl is that most accessors and mutators -- even for internal use -- are declared as public functions for various reasons

    • it's less complicated than checking caller
    • it's easier to type/read than calling as a private function -- as $self->foo vs _foo( $self )
    • historical convention and accessor generator modules have taught people that's how to do it.

    I generally feel that your comments about interfaces make sense. But whether you call them accessors or call them interfaces, the issue is the same. By tradition, Perl programmers frequently implement accessors and mutators as part of the published interface regardless of whether it really makes sense to do so from a "good design" standpoint. This is what leads to the sniping about "glorified structs" and so on. To some extent, I agree -- a programmer's convenience to avoid typos shouldn't be the driver of a public interface.

    Personally, I think that the distinction between an accessor and an interface is usually pretty minimal. This isn't to say that all internal properties should be exposed, but querying an object's state as expressed by its properties is frequently a necessary part of an interface. If the internal representation changes, the accessor becomes an interface that preserves compatibility.

    The stylistic/religious argument question as I see it is more about the mutability of properties once an object has been created -- whether the properties should ever be changed directly independent from the behavior of the object. To use the circle example, does a circle "change radius" or "change size"? One is a statement about a property and one is a behavior. (For this trivial example, they are mathematically equivalent, but not all cases are so trivial -- this is the peril of simplistic analogies.) On balance, I think that defining this interface in terms of the behavior makes for better design. E.g. this function defines a behavior and it can be parameterized in several ways regardless of the internal representation.

    $circle->resize( radius => 2 ); $circle->resize( area => 12.56 );

    However, no rule should be absolute, and I'm sure there are cases where breaking encapsulation and directly manipulating properties makes for a better/faster/whatever implementation. That said, the principle that I think makes the most sense for mutators is a paraphrase of Bill Clinton's statement on abortion (and apologies for bringing up politics should that offend or distract anyone): Mutators should be safe, legal and rare. (At least as part of the public interface.)

    As a related point, this is one reason why I've decided I'm not really in favor of the combined getter/setter style of accessors that are frequently seen in Perl. In addition to the ambiguities in usage that others (e.g. PBP) have noted, I think they make a poor default assumption that the interface for querying state should be the same one as for modifying state. Instead, for the circle, I'd rather see something like this:

    # Querying state -- which is the accessor and which are the interfaces +? $circle->radius(); $circle->diameter(); $circle->area(); # Modifying state -- interface potentially defines multiple acceptable + forms $circle->resize( radius => 3 ); $circle->resize( match_area => $another_circle );

    This keeps the user interface at the center of the design rather than having it be just a consequence of accessor generators.

    Update: Yes, it should have been run-time, not compile-time, on the typo checking. Thank you, Perl Mouse.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      Within the class package, accessors and mutators are heavily used for typo avoidance -- or rather, compile-time typo checking.
      Considering that Perl does look up of methods at run-time, what checking is done at compile-time?
      (And yes, inside-out objects address some of that, with yet other tradeoffs.)
      Nowadays, I usually use inside-out objects, and that means I don't write accessor all that often. I don't use objects if I want C-type structs, hashes will do fine in that case. For other cases I only provide methods to the outside world for those cases where the outside world might want to inspect (or set) some state of the object. This may be an accessor, but the outside world neither knows, nor has the need to know.

      I never use accessors to let a class get at its own data. IMO, there's no point in adding the overhead of calling a method to get a value from a hash, and I prefer the compile-time errors I get from making typos when accessing the data instead of the run-time errors when typoing a method name.

      For me, the existance of an accessor means that I intended the outside world to set, or look at a state. And that I hence should be careful when redoing the internals.

      Perl --((8:>*
        I never use accessors to let a class get at its own data.

        That's an interesting statement. I actually always use accesors when dealing with attributes to allow for subclasses to override how they wish to deal with an attribute. But, I tend to work either with datastructure-type classes like Tree where the algorithms of the class need to be separated from the datastorage of the class or with templating engines like Excel::Template where a given node doesn't know if it actually has the attribute or not. (Its parent might have the attribute, so it has to ask a separate context object where the attribute actually has a value.)


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Perl OO and accessors
by fergal (Chaplain) on Nov 29, 2005 at 01:41 UTC

    Presumably you'd also want

    get_radius() get_area()

    but then you could use the magic of lvalue subs and tie to let you replace these 4 functions with 2 accessors (it's much less messy in ruby or python)

    radius area

    so you could do

    $c->area = $c->area / 2; $c->radius = $c->radius * 2;

    Neither of these expose an attribute, despite how it looks. Whether the inplementation actually stores the radius or the area it doesn't matter, neither of these are exposed here, because all interaction is mediated by the methods.

    The only thing that is "evil" is unmediated (actually unmediatable) exposure of an attribute. In Java, where this notion of "evil" originates, there is no such thing as mediated exposure of an attribute. The evil in Java is that once an attribute is exposed, it cannot be unexposed and so the interface dictates the implementation forever more.

    In languages like Perl, Python, Ruby, Object Pascal and many others it is possible to create something that looks like an attribute but does not force anything on the implementation and so there is no evil. In some of these you can even expose an unmediated attribute and later turn it into a mediated attibute without changing the interface (hence the "unmediatable" above).

    So in some languages exposure is evil because the exposure is real and irrevocable but other languages allow you apparently expose or to revocably expose an attribute without forcing anything on the implementation.

      but then you could use the magic of lvalue subs and tie to let you replace these 4 functions with 2 accessors

      Using lvalue subs for accessors isn't generally a good idea. For instance, what would you do with $c->area = -42? And, how do you go back and change the interface later to avoid lvalue subs?

      -sauoq
      "My two cents aren't worth a dime.";
      
        . . . what would you do with $c->area = -42?

        use Math::Complex;

        ;-)

        After Compline,
        Zaxo

        what would you do with $c->area = -42?

        What you do with $c->set_area( -42 )?

        how do you go back and change the interface later to avoid lvalue subs?

        How do you go back and change the interface later to avoid set_foo methods?

        Makeshifts last the longest.

        That's why I said lvalue subs and tie.

        You don't think I'd write all the spiel about irrevocable exposure and then go and suggest a method that is irrevocable.

        The technique involves returning a tied lvalue which then calls a setter method with the new value. All this can be hidden behind a use statement. See Class::Accessor::Lvalue for an example.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://512359]
Front-paged by kutsu
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2024-04-20 00:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found