Encapsulate your attributes strongly. Perl 5's object orientation is minimalistic. It gives you enough to get the job done while not preventing you from doing clever things. Of course, the default approach is usually the simplest one (or the cleverest), not the cleanest or most maintainable. Most objects are blessed hashes, because they're easy to understand and to use. Unfortunately, they can be difficult to debug and they don't really provide any encapsulation, thus tying you to specific implementation schemes.[1]
Fortunately, fixing that is easy. The HackAn object in Perl needs two things, a place to store its instance data and a class in which to find its methods. A blessed hash (or array, or scalar, or subroutine, or typeglob, or...) stores its data within the object you pass around. If you dereference the reference, you can read and write that data from anywhere, even outside the class. An inside out object stores its data elsewhere, often in a lexical variable scoped to the class. From outside the lexical scope, you can't (usuallysee "Peek Inside Closures" [Hack #76]) access that data without using the object's accessors.
Running the HackA simple, naïve inside out object implementation for a record class might be: # create a new scope for the lexicals { package InsideOut::User; use Scalar::Util 'refaddr'; # lexicals used to hold instance data my %names; my %addresses; sub new { my ($class, $data) = @_; # bless a new scalar to get this object's id bless \\(my $self), $class; # store the instance data my $id = refaddr( $self ); $names{ $id } = $data->{name}; $addresses{ $id } = $data->{address}; return $self; } # accessors, as $self->{name} and $self->{address} don't work sub get_name { my $self = shift; return $names{ refaddr( $self ) }; } sub get_address { my $self = shift; return $addresses{ refaddr( $self ) }; } # many people forget this part sub DESTROY { my $self = shift; my $id = refaddr( $self ); delete $names{ $id }; delete $addresses{ $id }; } } 1; That's a little more typing, but it's definitely a lot cleaner. Now you can subclass or reimplement InsideOut::User without having to use a blessed hashjust follow the interface this defines and your code will work. Of course, the more complex the object, the more typing you have to do. Wouldn't it be nice to automate this? Hacking the HackClass::Std, Class::InsideOut, and Object::InsideOut are three current modules on the CPAN that take some of the work out of inside out objects for you. They all have various tricks and features. Class::Std is nice in that it automatically creates accessors and mutators, calls better constructors and destructors, and uses a declarative attribute-based syntax [Hack #45]. The same class using Class::Std is: { package InsideOut::User; use Class::Std; my %names :ATTR( :get<name> :init_arg<name> ); my %addresses :ATTR( :get<address> :init_arg<address> ); } This code automatically generates the get_name( ) and get_address( ) accessors as well as a constructor that pulls the initial values for the objects out of a hash reference with the appropriate keys. The syntax isn't quite as nice as that of Perl 6, but it's much, much shorter than the naïve Perl 5 versionand provides all of the same features. |