tags:

views:

260

answers:

2

Hi guys,

I am extending a module and I want some tips on good practices. Specially namespace conflicts: what exactly are they and how to avoid them.

When extending, should I not access variables in the SUPER class and only alter its state through accessors or object methods? What to do in case there are no (or limited) accessors? Am I "allowed" to access these object variables directly?

Cheers!

+6  A: 

It is best to only access things through accessors because this prevents changes in the implementation of the superclass from affecting the subclasses. You should stay far away from anything that starts with an underbar. Those things are private to the class. Try to stay away from anything that is not documented. Relying on those things will get you into trouble. Also, consider using has-a versus is-a relationship.

Let's imagine a widget class. This class has name and price members (note, none of this is particularly good code, I have just tossed of a version with out thinking about it for the sake of an example):

package Widget;

use strict;
use warnings;

sub new {
   my $class = shift;
   my %args  = @_;

   return bless {
       price => $args{price} || 0,
       name  => $args{name}  || "unkown",
   }, $class;
}

sub price { shift->{price} }
sub name  { shift->{name}  }

1;

You decide to subclass widget to add a weight member:

package Widget::WithWeight;

use strict;
use warnings;

use base 'Widget';

sub new {
    my $class = shift;
    my %args  = @_;
    my $self  = $class->SUPER::new(%args);
    $self->{weight} = $args{weight} || 0;
    return bless $self, $class;
}

sub weight { shift->{weight} }

sub price_per_pound {
    my $self = shift;
    return $self->{price}/$self->{weight};
}

1;

Now imagine the author of the first module changes his/her mind about how to store the price. Perhaps it was stored as a floating point number and the author realized that storing it as an integer number of pennies would be better:

package Widget;

use strict;
use warnings;

sub new {
   my $class = shift;
   my %args  = @_;

   if ($args{price}) {           
       $args{price} =~ s/[.]//;
   }

   return bless {
       price => $args{price} || "000",
       name  => $args{name}  || "unkown",
   }, $class;
}

sub price { 
    my $self = shift;
    my $price = $self->{price};
    substr($price, -2, 0) = ".";
    return $price;
}

sub name  { shift->{name}  }

1;

Suddenly, your tests will start failing, but if you had used the price accessor instead, you would have been insulated from that change.

Chas. Owens
I did not use the _foobar methods, however, I am accessing the internal representation of some variables directly, for instance:real life values are stored as hash refs like this:$self = {'property' => { objnum => NUM, type => TYPE, value => VALUE },}should I *not* access 'property' directly through $self, as in $self->{property}? Should I only access it if it had a get_property() accessor?Would 'requiring' a certaing version of the SUPER class module in my install script be an idiot thing to do?
Donato Azevedo
If they are not stored in the hash with an underbar (e.g. `$self->{_property}` they are fair game, but an accessor method is a safer way to go (protects you from implementation changes). Requiring a minimum version is not a bad idea.
Chas. Owens
+1  A: 

Namespace conflicts can happen if you inherit from two modules into one and they both provide (export) the same sub.

I suggest you have a look at Moose, an extension to Perl that provides you with classes and roles. You can avoid many conflicts if you use roles. See http://www.iinteractive.com/moose/

Moose also makes automatic accessors for the class variables, making it safer to access them from inheriting classes.

I am not inheriting from two modules, so that is not really the issue. I heard about Moose roles, they seem interesting indeed.However, the module I am extending is not mine (I am not the author) so I wouldn't do any good using Moose to create accessors for my subclass.Thanks for the suggestions though.
Donato Azevedo
Note, you can subclass non-`Moose` class with `Moose`, so there is nothing stopping you from using it. I would suggest `MooseX::Declare` over vanilla `Moose` though. It is much nicer.
Chas. Owens
What I meant was: I am creating class Foo::Bar, I did not create Foo, which is the class that does not implement accessors to some of its data. I'll get in touch with the author and see exactly why he does not want me poking that data... thanks for the moosex tip though!
Donato Azevedo