Do you have a question? Post it now! No Registration Necessary. Now with pictures!
- Posted on
- Rainer Weikusat
March 26, 2013, 4:52 pm
rate this thread
subroutine which expects 'an object' (a blessed reference) as argument
and does nothing with this object except invoking methods on it will
work with any object whose corresponding class ('package it is blessed
into _at the moment_') enables access to suitably name subroutines.
use Scalar::Util qw(blessed);
our @ISA = 'Named';
return bless(, $_);
return 'feline predator';
our @ISA = 'Named';
return bless($x, $_);
printf("The %s is a %s\n.", $_->name(), $_->what());
my ($j0, $j1);
$j0 = Brazilian::Jaguar->new();
$j1 = British::Jaguar->new();
Considering that such a subroutine is totally independent of any
package providing object methods, it should not be tied to one because
the code could be useful for all kinds of objects. Such a subroutine
really belongs into a general, non-OO subroutine library.
NB: The obvious implication is that no 'method' which only expects to
work with a 'thing' providing access to a certain set of 'named
properties' actually belongs to the 'the class proper'.
accessor methods (was: Perl method names are not tied to specific packages)
Out of curiosity, I did an internet search on 'accessor method' and
the result was mostly that people think they're supposed to provide
'public' access the state of an object BUT NOT because this enables
J. Random Get-shit-done-quickly to - well - get shit done quickly by
'hijacking' methods of an existing class with the help of supplanting
the implementation instead of using it as it was supposed to be used
according to its actual 'public interface'. The 'book' example Ben
Morrow posted was actually a good example of that: The so-called
'derived class' can only 'overload' the so-called 'methods' returning
the price and discount values because the guy who created the
so-called 'derived class' was familiar with the implementation of the
so-called 'superclass' to a sufficient degree to know that this change
wouldn't break anything.
Also, some of the concerns I voiced about that are by no means things
"only a totally crazy guy like XXX" could ever come up with: There's
an article on perl monks about that,
with the usual string of 'just let me fiddle with it, sweatheart'
'persuasion replies' attached and a more cogently written
text, although actually about Java, is available here:
This also includes some of the things I tried to express but
more-or-less failed, specifically in the idea that this essentially
turns an object into a 'C structure with higher overhead' and that
this is a natural idea for a procedural programmer trying to behave
'in an object-oriented way' for the sake of outward conformance.
Internal encapsulation [was: accessor methods]
OK, let me try one more time, because I think this is important. My
original point was not, primarily, about accessor methods, though it
came up in that context; it was more along the lines of 'methods should,
wherever reasonable, avoid relying on the internals of the object they are
called on, and instead use either the public interface or a well-defined
In Moose terms I might express this as 'as much code as possible should
be moved out of classes and into roles, which only rely on the object
implementing certain methods' since, as you have pointed out, when you
recast code like that these methods become, strictly speaking,
independent of the class they are applied to. However, absent Moose (or
some other implementation of roles), they end up going in the class
anyway, because there's no other way to get the dispatch to work right.
Let me give you an example of this: a real, non-contrived example, this
time, meaning it will require a bit of explanation. The Template Toolkit
(TT2) is a widely-used template system for Perl. It lets you write your
templates in a simple languge with constructions like
[% IF items.size %]
[% FOR i IN items %]
<tr><th>[% i.name %]</th><td>[% i.value %]</td></tr>
[% END %]
[% ELSE %]
<p>There are no items.
[% END %]
while abstracting out the details of accessing the data: for instance,
the values in the 'items' list can be hashrefs of precomputed data or
objects which calculate the values on the fly, and the '.' TT2 operator
will work correctly with either.
The list itself is what I'm interested in here. The usual way of passing
a list to a template is to pass an arrayref, and [% FOR %] will iterate
over the referenced array much like Perl's 'for my $i (@$list)'.
However, TT2 actually performs this iteration by creating a
Template::Iterator object pointing to the array, and you can (according
to the documentation) cause [% FOR %] to iterate over things other than
arrays by passing in a Template::Iterator object directly.
So, I had reason to want to pass in a 'list' which was actually an
object referencing an open database cursor. So I create a subclass of
Template::Iterator which holds a ref to a Cursor object (and, in fact,
since that's currently all it holds I implemented it as a blessed scalar
ref, but that's not really relevant), and I start looking at the methods
I need to implement.
get_next: this returns the next value from the iterator; my Cursor
has a ->next method, so this is easy.
index: this returns the index of the current iteration. Again, my
Cursor object counts rows already, so this is easy.
There are some more methods that aren't relevent here; the ones which
are relevent are
parity: this returns "odd" and "even" for alternate rows. If it did
this by calling ->index and using the result, I could just inherit
it; however, it doesn't do that, instead it accesses the internal
-> attribute directly. This means I have to reimplement
effectively the same logic again, for no good reason.
odd and even: these return true or false depending on the parity.
Again, if they were implemented by calling ->parity and using that,
I could ignore them; but they're not, so I need to reimplement these
first: this returns true if we are on the first row, false
otherwise. Unlike ->parity, which seems pretty useless to me, this
is actually quite useful, for situations like
[% FOR i IN items %]
[% IF loop.first %]<p>[% ELSE %]<br>[% END %][% i %]
[% END %]
('loop' is a magic TT2 variable which refers to the
Template::Iterator object for the current loop). Again, I would say
this ought to have been implemented as '->index() == 0'; and again,
it's actually implemented by accessing the underlying hashref, so if
I want it to work for my subclass I have to override it.
I seem to find this all the time with non-Moose Perl classes. Too often
they just aren't designed to have a clean subclassing interface, and one
of the reasons for that is this habit of accessing the underlying data
structure directly. At least if there's a layer of accessor methods
there's a *chance* you can keep the existing methods which do actual
That article would be better titled 'Why get() and set() accessor
methods should not usually be part of an object's public external
interface', and that is a sentiment I entirely agree with, particularly
for setters (or getters which return modifiable references). I am
talking about using them as part of the internal subclassing interface,
which is entirely different: the alternative is to force subclasses to
maintain the same internal representation, so this is actually
increasing encapsulation. (And no, I don't mean 'arrayref vs hashref', I
mean something more like the situation above, where what was previously
an attribute of the object itself becomes an attribute of an attribute.)
I find the end of that article interesting: the author basically points
out that this high-minded OO design stuff only really applies when
designing a closed system, where you know in advance who all your users
will be. Perl culture is strongly biased towards writing and then
reusing generic components, rather than redoing the design and
implementation for each specific case, and in this situation what that
author calls 'unnecessary flexibility' is, in fact, a design
requirement. To my mind an important part of this is providing and
consistently using methods (such as accessors, but they are certainly
not the only case) which don't apparently do very much, but provide a
way for subclasses to make small changes and get usefully and
consistently different behaviour.