How to override a sub in a Moose::Role?
Asked Answered
G

2

7

I'm trying to implement a Moose::Role class that behaves like an abstract class would in Java. I'd like to implement some methods in the Role, but then have the ability to override those methods in concrete classes. If I try this using the same style that works when I extend classes I get the error Cannot add an override method if a local method is already present. Here's an example:

My abstract class:

package AbstractClass;

use Moose::Role;

sub my_ac_sub {

    my $self = shift;

    print "In AbstractClass!\n";
    return;
}

1;

My concrete class:

package Class;

use Moose;

with 'AbstractClass';

override 'my_ac_sub' => sub {

    my $self = shift;

    super;
    print "In Class!\n";
    return;
};

__PACKAGE__->meta->make_immutable;
1;

And then:

use Class;

my $class = Class->new;
$class->my_ac_sub;

Am I doing something wrong? Is what I'm trying to accomplish supposed to be done a different way? Is what I'm trying to do not supposed to be done at all?

Gamelan answered 23/9, 2013 at 12:40 Comment(9)
Use an abstract class to model an abstract class! This simply requires you to make construction impossible (i.e. provide a BUILDALL that throws an error).Lapboard
Probably the Moose way of doing this is to have requires 'my_ac_sub'; in the Role, not the "virtual" method. Moose::Role will then check it has been composed into a class with the method available,Offenbach
I've tried running the code and then replacing override by just sub my_ac_sub and suddenly it worked just as expected. Is there anything wrong with that "fix"? (Disclaimer: I'm new to Moose).Varietal
Nothing, except the base class is no longer providing an "abstract" method. It's just a regular base class, is that what you are looking for?Offenbach
@Lapboard I think I know what you're talking about, I just can't seem to find an example of it right now. I seem to remember something about 'call BUILDALL and bail if the class was directly instantiated, or continue if something was subclassing it' - correct?Gamelan
@NeilSlater Not quite. I want other implementers/inheritors of the abstract class not to have to implement that method. I'd like to provide a default implementation, but allow subclasses to override it if they want - but not allow an object of AbstractClass to be created.Gamelan
@Varietal I tried it and it doesn't work like I would like. I does print "In Class!" but it doesn't print "In AbstractClass!" like I would like.Gamelan
@Gamelan Oh I see, I didn't get the super() part. But then, roles don't get into @ISA anyway and super seems to use that... As I understand, "not allowing to be created" can be achieved by before "new" => sub { $_[0] eq __PACKAGE__ and die "Abstract" };, but that's a hack and there must be something better.Varietal
And it's a performance hit in all descendant classes, too. I'm willing to see a real solution.Varietal
G
5

Turns out I was using it incorrectly. I opened a ticket and was shown the correct way of doing this:

package Class;

use Moose;

with 'AbstractClass';

around 'my_ac_sub' => sub {

    my $next = shift;
    my $self = shift;

    $self->$next();
    print "In Class!\n";
    return;
};

__PACKAGE__->meta->make_immutable;
1;

Making this change has the desired effect.

Gamelan answered 8/10, 2013 at 12:53 Comment(0)
M
2

Some time ago, I did this by having a role that consists solely of requires statements. That forms the abstract base class. Then, you can put your default implementations in another class and inherit from that:

#!/usr/bin/env perl

use 5.014;

package AbstractClass;

use Moose::Role;

requires 'my_virtual_method_this';
requires 'my_virtual_method_that';

package DefaultImpl;

use Moose;
with 'AbstractClass';

sub my_virtual_method_this {
    say 'this';
}

sub my_virtual_method_that {
    say 'that'
}

package MyImpl;

use Moose;
extends 'DefaultImpl';
with 'AbstractClass';

override my_virtual_method_that => sub {
    super;
    say '... and the other';
};

package main;

my $x = MyImpl->new;

$x->my_virtual_method_this;
$x->my_virtual_method_that;

If you want to provide default implementations for only a few methods define in the role, remove the requires from DefaultImpl.

Output:

$ ./zpx.pl
this
that
... and the other
Macario answered 23/9, 2013 at 20:41 Comment(1)
I think this is a good work around. It doesn't "feel clean", but I'll submit a wishlist enhancement for this. Thank you for this answer.Gamelan

© 2022 - 2024 — McMap. All rights reserved.