While refactoring I'm trying to retain some backwards compatibility for a time. I'm wondering if it's possible to have a method on an object, but prevent that method from being inherited by classes that subclass it? e.g. given
package Class {
use Moose;
sub foo { 'test' };
}
my $class = Class->new;
$class->foo;
would work, but
package Extended::Class {
use Moose;
extends 'Class';
}
my $class = Extended::Class->new;
$class->foo;
would not.
I realize this probably breaks some principle or another, but I'm deprecating these interfaces as I go.
How about:
use 5.014;
package Class {
use Carp qw( croak );
use Moose;
sub foo {
my $self = shift;
croak unless __PACKAGE__ eq ref $self;
return 'test';
}
}
package Extended::Class {
use Moose;
extends 'Class';
}
package main {
my $x = Class->new;
say $x->foo;
my $y = Extended::Class->new;
say $y->foo;
}
Have you considered delegation?
package Original {
use Moose;
sub foo { 23 }
sub bar { 42 }
}
package Subclass {
use Moose;
has original => (
buidler => '_build_original',
handles => [qw( bar )],
);
sub _build_original { Original->new }
}
Of course it depends on your situation if you can use it. The subclass won't pass isa checks for the above (but you can override isa if you must). Also passing the original arguments on to the object you're extending can be annoying depending on the use case.
Since it would look for the method foo in the Extended::Class first, you could just declare one there that doesn't do anything. That way the inherited one would not be called unless you do so somewhere in your subclass.
I'm not sure if Moose alters that behaviour, though.
package Class {
use Moose;
sub foo { 'test' }
}
package Extended::Class {
use Moose;
extends 'Class';
sub foo {
# do nothing
}
}
package main {
my $x = Class->new;
my $y = Extended::Class->new;
print $x->foo;
print $y->foo;
}
Related
I have a class X with a subclass Y. X has a method calculate() that I'd like to override in Y with some additional behaviour, an if statement that, if it fails, call X.calculate(). In Python this would be accomplished with:
class X(object):
def calculate(self, my_arg):
return "Hello!"
class Y(X):
def calculate(self, my_arg):
if type(my_arg) is int and my_arg > 5:
return "Goodbye!"
return super(Y, self).calculate(my_arg)
How can I do this in Perl using the Moo module?
As the docs point out:
No support for super, override, inner, or augment - the author considers augment to be a bad idea, and override can be translated:
around foo => sub {
my ($orig, $self) = (shift, shift);
...
$self->$orig(#_);
...
};
(emphasis mine)
#!/usr/bin/env perl
use strict;
use warnings;
package X;
use Moo;
sub calculate {
return 'Hello!'
}
package Y;
use Moo;
extends 'X';
around calculate => sub {
my $orig = shift;
my $self = shift;
if ( $_[0] > 5 ) {
return $self->$orig(#_);
}
return 'Goodbye!';
};
package main;
my $y = Y->new;
print $y->calculate(3), "\n";
print $y->calculate(11), "\n";
This can be done in Perl via the SUPER:: pseudo-class, which is part of Perl's method resolution system. You just put it in front of the method-call. It does not work for class methods or function calls.
use strict;
use warnings;
use feature 'say';
package Foo;
use Moo;
sub frobnicate {
my $self = shift;
say "foo";
}
package Bar;
use Moo;
extends 'Foo';
sub frobnicate {
my $self = shift;
say "bar";
$self->SUPER::frobnicate;
}
package main;
Bar->new->frobnicate;
You can even use this to call each parent's method if you have multi-level inheritance.
package Grandparent;
sub foo { ... }
package Parent;
use parent 'Grandparent';
sub foo { $_[0]->SUPER::foo }
package Child;
use parent 'Parent';
sub foo { $_[0]->SUPER::foo }
This will subsequently call foo in Child, Parent and Grandparent.
I am starting to learn about objects in Perl using Moose.
I am not sure if I understand the purpose of MooseX::Privacy. Consider:
use v5.14;
package PA {
use Moose;
my $var='private?';
1;
sub getVar {
return $var;
}
}
package PB {
use Moose;
use MooseX::Privacy;
has 'var' => (
is => 'rw',
isa => 'Str',
default => 'private?',
traits => [qw/Private/],
);
1;
sub getVar {
my $self = shift;
return $self->var;
}
}
my $o1= PA->new();
my $o2= PB->new();
say $o1->getVar();
say $o2->getVar();
In both class PA and PB I have a private variable var. Only in class PB I use MooseX::Privacy. What is the difference between these two approaches? And why should I use MooseX::Privacy?
If you're looking for Java-style method privacy, then MooseX::Privacy is going to be a big disappointment. Here's what happens with Java style method privacy:
/* This file is called Main.java */
public class Main
{
public class MyParent
{
private String message_string ()
{
return "Message from %s\n";
}
public void print_message ()
{
System.out.printf( this.message_string(), "MyParent" );
}
}
public class MyChild extends MyParent
{
public String message_string ()
{
return "Another message from %s\n";
}
}
public static void main (String[] args)
{
Main o = new Main();
o.run();
}
public void run ()
{
MyParent c = new MyChild();
c.print_message();
}
}
You can compile and run this example like this:
$ javac Main.java
$ java Main
Message from MyParent
Note what's happened. The parent class (MyParent) declares message_string() to be a private method. The child class attempts to override the method but is roundly rebuffed - no soup for you child class!
Now let's try the equivalent with Perl and MooseX::Privacy...
# This file is called Main.pl
use v5.14;
use strict;
use warnings;
package MyParent {
use Moose;
use MooseX::Privacy;
private_method message_string => sub {
my $self = shift;
return "Message from %s\n";
};
sub print_message {
my $self = shift;
printf($self->message_string(), __PACKAGE__);
}
}
package MyChild {
use Moose; extends qw(MyParent);
use MooseX::Privacy;
sub message_string {
my $self = shift;
return "Another message from %s\n";
}
}
my $c = new MyChild();
$c->print_message();
We can run that like this:
$ perl Main.pl
Another message from MyParent
Say, WHA?!?!?! Ain't message_string supposed to be private?! How the hell did MyChild override the method in MyParent?!
The fact of the matter is, MooseX::Privacy doesn't give you anything close to method privacy as implemented in most OO languages. MooseX::Privacy is simply akin to doing this in your method:
die "GO AWAY!!" unless caller eq __PACKAGE__;
Except that MooseX::Privacy adds massive runtime expense to all your method calls.
Really, there's little reason to use MooseX::Privacy. If you want private methods, put them in lexical variables. Like this:
use v5.14;
use strict;
use warnings;
package MyParent {
use Moose;
my $message_string = sub {
my $self = shift;
return "Message from %s\n";
};
sub print_message {
my $self = shift;
printf($self->$message_string(), __PACKAGE__);
}
}
package MyChild {
use Moose; extends qw(MyParent);
sub message_string {
my $self = shift;
return "Another message from %s\n";
}
}
my $c = new MyChild();
$c->print_message();
Now run it:
$ perl Main2.pl
Message from MyParent
Hallelujah!! We have a true private method!
OK, so you can have private methods without MooseX::Privacy, and they work better (and faster) than MooseX::Privacy.
But what about private attributes? Well, I have a little module on CPAN that can help you: Lexical::Accessor. This is a little tool that creates an attribute for you, with "inside out" storage (i.e. the attribute value doesn't get stored in the object's blessed hashref), and installs the accessors for it in lexical variables (just like the private $get_message method above).
Anyway, that's my opinion on MooseX::Privacy.
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?
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.
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
Say that I have two roles: Simple::Tax and Real::Tax. In testing situations, I want to use Simple::Tax, and in production, I want to use Real::Tax. What is the best way to do this? My first thought was to use different versions of the new method to create objects with different roles:
#!/usr/bin/perl
use warnings;
{
package Simple::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
use Moose::Util qw( apply_all_roles );
has price => ( is => "rw", isa => 'Int' ); #price in pennies
sub new_with_simple_tax {
my $class = shift;
my $obj = $class->new(#_);
apply_all_roles( $obj, "Simple::Tax" );
}
}
my $o = A->new_with_simple_tax(price => 100);
print $o->calculate_tax, " cents\n";
My second thought was to use an if statement in the body of package to use different with statements:
#!/usr/bin/perl
use warnings;
{
package Complex::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
#pretend this is more complex
return int($self->price * 0.15);
}
}
{
package Simple::Tax;
use Moose::Role;
requires 'price';
sub calculate_tax {
my $self = shift;
return int($self->price * 0.05);
}
}
{
package A;
use Moose;
has price => ( is => "rw", isa => 'Int' ); #price in pennies
if ($ENV{TEST_A}) {
with "Simple::Tax";
} else {
with "Complex::Tax";
}
}
my $o = A->new(price => 100);
print $o->calculate_tax, " cents\n";
Is one of these better than the other, is there something horrible about either of them, and is there a better way I haven't thought of yet.
My first suggestion would be something like MooseX::Traits and then specify the different roles at object creation:
my $test = A->with_traits('Simple::Tax')->new(...);
my $prod = A->with_traits('Complex::Tax')->new(...);
But this opens the door to an A being created without either Role being applied. So thinking about it further, I think you've got an X/Y problem. If Simple::Tax is only ever used to mock up Complex::Tax in a test environment you can do several things to override the Complex::Tax implementation.
For example you could just define Simple::Tax like so:
package Simple::Tax;
use Moose::Role;
requires 'calculate_tax';
around calculate_tax => sub { int($_[1]->price * 0.05) };
Then always have A compose Complex::Tax and apply Simple::Tax to it only during tests (using apply_all_roles).
If however you need Simple::Tax and Complex::Tax both in production (and not simply for testing) your best bet is refactor from a composition relationship (does) to a delegation relationship (has).
package TaxCalculator::API;
use Moose::Role;
requires qw(calculate_tax);
package SimpleTax::Calculator;
use Moose;
with qw(TaxCalculator::API);
sub calculate_tax { ... }
package ComplexTax::Calculator;
use Moose;
with qw(TaxCalculator::API);
sub calcuate_tax { ... }
package A;
use Moose;
has tax_calculator => (
does => 'TaxCalculator::API',
handles => 'TaxCalculator::API',
default => sub { ComplexTax::Calculator->new() },
);
Then if you want to override it you simply pass in a new tax_calculator:
my $test = A->new(tax_calculator => SimpleTax::Calculator->new());
my $prod = A->new(tax_calculator => ComplexTax::Calculator->new());
Because handles will delegate all of the methods from the role as new proxies this is practically identical to having composed the role yourself.
I need to define multiple modules in the same file. I would like to do something like the following:
package FooObj {
sub new { ... }
sub add_data { ... }
}
package BarObj {
use FooObj;
sub new {
...
# BarObj "has a" FooObj
my $self = ( myFoo => FooObj->new() );
...
}
sub some_method { ... }
}
my $bar = BarObj->new();
However, this results in the message:
Can't locate FooObj.pm in #INC ...
BEGIN failed...
How do I get this to work?
Drop the use. Seriously.
use tells perl to read in the code from another file, which you don't need to do because the code is in the same file.
Unless I'm trying to create a private package that no one should know about, I put one package per file. That solves the problem. But, let's put them in the same file.
The use loads a file and calls the import method in that package. It's really only incidently that its argument looks like a module name. It's looking for the file. If the file is not there, it barfs.
You can do this, where BarObj assumes that FooObj is already there:
{
package FooObj;
sub new { bless { _count => 0 }, $_[0] }
sub add_data { $_[0]->{_count}++ }
}
{
package BarObj;
use Data::Dumper;
sub new {
bless { myFoo => FooObj->new }, $_[0];
}
sub foo { $_[0]->{myFoo} }
sub some_method { print Dumper( $_[0] ) }
}
my $bar = BarObj->new;
$bar->some_method;
If you need to interact with a package (and that's all it is: not a module or an object), you just need to have it defined before you want to use it. If you need to import something, you can call the import directly:
FooObj->import( ... );
Suppose there's something from FooObj that you want to import (but not inherit!), you call import directly with no loading;
{
package FooObj;
use Data::Dumper;
sub new { bless { _count => 0 }, $_[0] }
sub add_data { $_[0]->{_count}++ }
use Exporter qw(import);
our #EXPORT = qw(dumper);
sub dumper { print Dumper( $_[0] ) }
}
{
package BarObj;
FooObj->import;
sub new {
bless { myFoo => FooObj->new }, $_[0];
}
sub foo { $_[0]->{myFoo} }
# dumper mixin, not inherited.
sub some_method { dumper( $_[0] ) }
}
my $bar = BarObj->new;
$bar->some_method;
By convention we put one package in one file and name them the same thing, but that is just for convenience. You can put multiple packages in a single file. Since they are already loaded, you do not need to use use.
You also do not need to create special scoping for the packages, as the package keyword takes care of that. Using the braces does help with scoping of our variables. So you don't strictly need those brace blocks, but they're a good idea.
use uses a package naming convention to find the appropriate file to load. The package keyword inside the module defines the namespace. And the import functions handle the package loading (generally inherited from Exporter).
#!/usr/bin/perl
use strict;
use warnings;
package FooObj;
sub new
{
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
bless $self, $class;
$self->initialize();
return $self;
}
sub initialize { }
sub add_data { }
package BarObj;
#use FooObj; <-- not needed.
sub new
{
my $this = shift;
my $class = ref($this) || $this;
my $self = { myFoo => FooObj->new() };
bless $self, $class;
$self->initialize();
return $self;
}
sub initialize { }
sub some_method { }
sub myFoo { return $_[0]->{myFoo} }
package main;
use Test::More;
my $bar = BarObj->new();
isa_ok( $bar, 'BarObj', "bar is a BarObj" );
isa_ok( $bar->myFoo, 'FooObj', "bar->myFoo is a FooObj" );
done_testing();
__DATA__
ok 1 - bar is a BarObj isa BarObj
ok 2 - bar->myFoo is a FooObj isa FooObj
1..2