betacentauri has asked for the wisdom of the Perl Monks concerning the following question:

Hi, Monks

How do I properly extend a class?

I was dreaming of creating my own SVG drawing class having SVG's inherited methods and then mine. As the wiser than me can foresee, the accompanying code spits out:

Can't locate object method "Foo" via package "SVG::Element" at ./test. +pl line 17.

Of course, the message refers to the last line in Bar().

Does this mean I should mimic every SVG method I need into MyPackage (possibly by way of __SUPER__:: calls)? Or should I make $g behave as a MyPackage object by way of casting? Or?

#!/usr/bin/perl use warnings; use strict; package MyPackage; use parent 'SVG'; sub Foo { my ($self, $fooArg) = @_; return $self->rectangle(id => $fooArg, x => 10, y => 10); } sub Bar { my ($self, $barArg) = @_; my $g = $self->group(id => $barArg); return $g->Foo("g$barArg"); } package main; my $s = MyPackage->new(); $s->Foo("someID"); $s->Bar("someOtherID"); print $s->xmlify(namespace => 'svg');

Replies are listed 'Best First'.
Re: Extending a class (updated x2)
by haukex (Archbishop) on Apr 13, 2016 at 20:17 UTC

    Hi betacentauri,

    In sub Bar, you're calling $g->Foo, where $g is the return value of $self->group. Looking at the code of SVG::Element, that's a new SVG::Element. SVG::Element doesn't have a sub Foo, which is why the call fails.

    As suggested by the AM, one solution is to patch the methods into SVG::Element by defining them as sub SVG::Element::Foo { ... }, MyPackage isn't necessary in that case. (Note however that a new constructor for MyPackage is not required, as the AM seems to be saying; the constructor is indeed inherited from SVG, which in turn calls its parent constructor in SVG::Element, but since they are coded appropriately they give you an object of type MyPackage.) It's kind of inelegant but it works. If you're only adding a few methods, it might be an acceptable solution.

    Other than that, since SVG::Element::tag() is hardcoded to return a new object of type SVG::Element, I can't at the moment think of a solution that doesn't involve either updating the SVG package itself, or some kind of inelegant hacking/patching (perhaps another monk has another alternative handy).

    Update: You can also fix the immediate problem by writing return $g->MyPackage::Foo("g$barArg"); in Bar. This doesn't stop Foo and Bar from returning objects of type SVG::Element, but at least it gets your example code working... now I'm out of ideas for tonight. (I also tried reblessing the return values, but that caused the SVG to be missing elements, which I haven't looked into yet.)

    Update 2: SVG::Element uses the code if ( ref($k) =~ /^SVG::Element/ ) (instead of isa) to check whether objects are of its type. I'd say this and the above make it too difficult to do normal subclassing.

    Hope this helps,
    -- Hauke D

      In sub Bar, you're calling $g->Foo, where $g is the return value of $self->group. Looking at the code of SVG::Element, that's a new SVG::Element. SVG::Element doesn't have a sub Foo, which is why the call fails.

      Yes! I was aware of that. But I was expecting that, since $self was a MyPackage object, its progeny would be the same species...

      Anyway the SVG::Element::Foo trick seems to work, thank you all very much for your responses!

        Hello betacentauri,

        But I was expecting that, since $self was a MyPackage object, its progeny would be the same species...

        Yes, they would be. So within your sub Bar, $self is an instance of class MyPackage; and since MyPackage ISA SVG, $self ISA SVG too.

        But don’t be fooled by the names: as far as Perl is concerned, SVG and SVG::Element are separate and unrelated classes! When Perl sees use SVG; (or use parent 'SVG';), it looks for a module named SVG.pm in the directories contained in the global variable @INC. But when it sees use SVG::Element;, it looks in @INC for a directory named SVG, and then within that directory for a module named Element.pm. And that’s it! Apart from their namespaces, the two modules are independent of each other. See perlmod#Packages.

        Of course, these classes are related conceptually (i.e., in the programmer’s mind), which is why their author included them both in the SVG distribution (which also contains classes SVG::DOM, SVG::Extension, and SVG::XML); but Perl itself knows nothing of this relationship.

        Update: The above is true in the general case. But in this specific case, it turns out that the module SVG.pm actually begins as follows:

        package SVG; use strict; use warnings; use SVG::XML; use parent qw(SVG::Element SVG::Extension);

        — so an object of class SVG ISA SVG::Element as well. Thanks to haukex for the heads-up.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Hi betacentauri,

        I was expecting that, since $self was a MyPackage object, its progeny would be the same species...

        my $tag = new SVG::Element( $name, %attrs ); is hardcoded in SVG::Element::tag() (called directly from SVG::Element::group()), so it doesn't depend on $self, it's always calling SVG::Element::new(). It could certainly be argued that SVG::Element could be coded differently to enable the kind of subclassing you want to do!

        Regards,
        -- Hauke D

Re: Extending a class
by SimonPratt (Friar) on Apr 13, 2016 at 20:08 UTC

    You're doing the right things. I suggest you pull main out into a separate file - This has caused me grief in the past when writing classes involving inheritance.

    This works perfectly for me:

    #!/usr/bin/perl use warnings; use strict; package MyPackage; use parent 'SVG'; sub Foo { my ($self, $fooArg) = @_; return $self->rectangle(id => $fooArg, x => 10, y => 10); } sub Bar { my ($self, $barArg) = @_; my $g = $self->group(id => $barArg); return $g->Foo("g$barArg"); } 1;

    EDIT: Actually, looking a bit closer, you're doing $g = $self->group... This is returning a SVG::Element object, which of course you cannot call Foo on, as Foo is a function of MyPackage

Re: Extending a class
by Anonymous Monk on Apr 13, 2016 at 15:34 UTC

    You are missing a constructor (sub MyPackage::new), search for "sub new" in Modern Perl or "sub new"

    You can squat/monkeypatch like  sub SVG::Element::Foo { ... } which can work well for one-offs/scripts... and oftentimes is the only reasonable way to extend some classes

      Thank you for your reply! However, isn't SVG::new() already in place anyway? The MyPackage object builds and yields a MyPackage-blessed object as revealed by Data::Dumper.

      I'm checking the book by the way!

        However, isn't SVG::new() already in place anyway? The MyPackage object builds and yields a MyPackage-blessed object as revealed by Data::Dumper.

        :) ok, if the constructor is creating a MyPackage, then which code is creating a SVG::Element?

        Think a bout it