http://qs1969.pair.com?node_id=609197

Here's the scenario: You have a class implemented in the hash-based tradition. Code using it is welcome to access certain public attributes directly (e.g., $object->{foo} = 'new value of foo' is supported). Now you want to change that class to be an inside-out object, but you don't want to break existing code.

One answer is to use overload to make your new inside-out object appear to be a hash-based object. The code below is a proof of concept.

package Hash::Pretender; use overload '%{}' => \&get_hashref; use Class::Std; { my %attr1_of :ATTR( :get<attr1> :set<attr1> ); my %attr2_of :ATTR; sub get_hashref { my $self = shift; tie my %h, ref $self, $self; return \%h; } sub get_attr2 { return ++$attr2_of{ ident shift }; } sub TIEHASH { my ( $package, $self ) = @_; return $self; } sub STORE { my ($self, $key, $value) = @_; my $setter = "set_$key"; $self->$setter( $value ); } sub FETCH { my ($self, $key) = @_; my $getter = "get_$key"; return $self->$getter(); } } package main; use Test::More 'tests' => 11; my $o = Hash::Pretender->new(); isa_ok( $o, 'Hash::Pretender' ); $o->set_attr1( 'foo' ); is( $o->get_attr1(), 'foo', 'attr1 is "foo" via get_attr1' ); is( $o->{attr1}, 'foo', 'attr1 is "foo" via hash dereference' ); SKIP: { skip 'Not implemented', 1 unless ( $o->can('FIRSTKEY') && $o->can('NEXTKEY') ); %h = %{$o}; is( $h{attr1}, 'foo', 'attr1 is "foo" via hash copy' ); }; $o->{attr1} = 'bar'; is( $o->get_attr1(), 'bar', 'attr1 is "bar" via get_attr1' ); is( $o->{attr1}, 'bar', 'attr1 is "bar" via hash dereference' ); SKIP: { skip 'Not implemented', 1 unless ( $o->can('FIRSTKEY') && $o->can('NEXTKEY') ); %h = %{$o}; is( $h{attr1}, 'bar', 'attr1 is "bar" via hash copy' ); }; is( $o->get_attr2(), 1, 'attr2 is 1' ); is( $o->{attr2}, 2, 'attr2 is 2' ); ok( ! eval { $o->{attr2} = 3; 1 }, 'Set attr2 fails' ); my $err = $@; my $msg = q{Can't locate object method "set_attr2" via package "Hash::Pret +ender"}; is( substr( $err, 0, length $msg), $msg, 'Set attr2 fails for the expected reason' );

Now, code that used the old interface will continue to work. If you want to migrate it, you can throw out warnings in the methods that tie depends on to track down the offending code. You can also continue using the old interface and gain the advantage that the new code will throw an exception when you typo an attribute name.

Replies are listed 'Best First'.
Re: Make an inside-out object look hash-based using overload.
by Ovid (Cardinal) on Apr 11, 2007 at 09:14 UTC

    Nice to see tests along with this.

    I was halfway expecting folks to complain that you're violating the entire point of IOO (Inside-Out Objects), but I've worked with enough dodgy code that I know things like this are frequently necessary to evolve a code base into something useful.

    Cheers,
    Ovid

    New address of my CGI Course.

Re: Make an inside-out object look hash-based using overload.
by adrianh (Chancellor) on Apr 11, 2007 at 12:35 UTC