views:

57

answers:

4

I'm working with a few Perl packages, we'll call them Some::Parser and Some::Data. A Some::Parser object has methods to return objects of type Some::Data. I have written a class that extends the Some::Data class, let's call it My::Data. Objects of class My::Data are really just objects of class Some::Data, but with additional methods that make it easier to work with.

My problem is that I want to continue to use the Some::Parser class to do the hard work of parsing the data. As I said earlier, Some::Parser objects give me Some::Data objects. Once I have a Some::Data object in hand, is there any way to reclassify it as a My::Data object? How would I do this?

Thanks!

PS. I'm totally willing to change my approach, assuming someone can suggest a better way of doing what I want to do. Remember, writing my own parser is not something I'm interested in doing!

+3  A: 

You can rebless anything.

Inheritance in Perl 5 is nothing more than searching @ISA.

Andy Lester
+8  A: 

This smells like a bit of a kludge. It might be time to rethink your strategy. For example, maybe you should write My::Parser which returns My::Data objects.

But if you don't want to do that, you can manually use bless to change an object's class:

my $obj = Some::Data->new;
bless $obj, 'My::Data';

See bless in perldoc.

friedo
Since I'm still in the early stages, it's the perfect time to rethink my strategy...that's why I'm asking! I've been reading a lot about bless, and people have been warning about reblessing objects. Will this be less of an issue if the new object class extends the original class?
Daniel Standage
I think extending the original class is probably the best way to go. (As long as the original class is easily extensible. Don't go nuts if it's a small problem and re-blessing will solve it.) You can write your own constructor in your derived class which will bless the object. Your constructor may need to call the parent class constructor first and then re-bless, which amounts to the same thing but provides a nicer interface.
friedo
+3  A: 

You can re-bless the returned object to whatever your heart desires:

#!/usr/bin/perl

package Some::Data;
use strict; use warnings;

sub new { my $class = shift; bless { @_ } => $class }

sub a { $_[0]->{a} }

package My::Data;
use strict; use warnings;
use base 'Some::Data';

sub a_squared {
    my $self = shift;
    my $v = $self->a;
    return $v * $v;
}

package Some::Parser;
use strict; use warnings;

sub new { my $class = shift; bless { @_ } => $class }

sub parse { return Some::Data->new(a => 3) }

package main;

use strict; use warnings;

my $data = Some::Parser->new->parse;
bless $data => 'My::Data';

printf "%.1f\t%.1f\n", $data->a, $data->a_squared;

Alternatively, you can use @cjm's idea:

#!/usr/bin/perl

package Some::Data;
use strict; use warnings;

sub new { my $class = shift; bless { @_ } => $class }

sub a { $_[0]->{a} }

package My::Data;
use strict; use warnings;
use base 'Some::Data';

sub a_squared {
    my $self = shift;
    my $v = $self->a;
    return $v * $v;
}

package Some::Parser;
use strict; use warnings;

sub new { my $class = shift; bless { @_ } => $class }

sub parse {
    my $self = shift;
    return $self->data_class->new(a => 3);
}

sub data_class { $_[0]->{data_class} }

package main;

use strict; use warnings;

my $data = Some::Parser->new(data_class => 'My::Data')->parse;
printf "%.1f\t%.1f\n", $data->a, $data->a_squared;
Sinan Ünür
+5  A: 

Probably the best way to handle something like this is for Some::Parser to provide a way to specify the class it should be using for data objects. For example, HTML::TreeBuilder provides the element_class method. If you want TreeBuilder to produce something other than HTML::Element nodes, you subclass HTML::TreeBuilder and override element_class to return your desired node class. (The actual code in TreeBuilder is a bit more complex, because there was a different mechanism for doing this prior to HTML-Tree 4, and the new maintainer didn't want to break that.)

I take it that you didn't write Some::Parser, but perhaps it has this capability already. If not, maybe its maintainer will accept a patch. It should be a fairly simple change. You'd just add a data_class method (sub data_class { 'Some::Data' }), and then change Some::Data->new to $self->data_class->new. Then you can subclass Some::Parser to create My::Parser, and just override data_class.

cjm