Extending a Perl non-Moose respecting encapsulation - perl

I have a legacy project and I would like to extend a couple of classes in it with a few attributes and methods. I have access to the source code and know that the class uses a blessed hashref. I can of course go ahead and extend that hashref adding the keys that I want and re-bless into my class. But obviously this breaks encapsulation and I would like to avoid it as much as I can.
Is there a way to extend a (non-Moose) Perl class with attributes, not just methods, in a way that does not break encapsulation of the orginal class? The option to use Moose to do this is not available. Thank you.

First, one best practice for writing objects based on hashrefs is to prefix all fields with the package name, e.g.
package Parent;
sub new {
my ($class, $x, $y) = #_;
bless { "Parent::x" => $x, "Parent::y" => $y } => $class;
}
sub x { shift()->{"Parent::x"} }
sub y { shift()->{"Parent::y"} }
In that case, the issue doesn't arise, as every class has its own attribute namespace. But who writes his classes that way?
There are two ways I can think of to circumvent any problems: Proxying the original object via Autoload, or using inside-out object patterns. The third solution is to use prefixed attributes in your class, and hope that the parent never ever uses these names.
Inside-Out Objects
An inside-out object uses the blessed reference as an ID, and stores the attributes in lexical variables inside your class:
package Child;
use Scalar::Util qw/refaddr/;
use parent 'Parent';
my %foo;
sub new {
my ($class, $foo, #args) = #_;
my $self = $class->SUPER::new(#args);
$foo{refaddr $self} = $foo;
return $self;
}
sub foo {
my ($self) = #_;
$foo{refaddr $self};
}
sub set_foo {
my ($self, $val) = #_;
$foo{refaddr $self} = $val;
}
sub DESTROY {
my ($self) = #_;
# remove entries for this object
delete $foo{refaddr $self};
$self->SUPER::DESTROY if $self->SUPER::can('DESTROY');
}
This is a slightly dated pattern, but it works extremely well for your use case.
Proxy objects
We can contain a parent instance in a field of our class (i.e. both has-a and is-a relationship). Whenever we encounter unknown methods, we delegate to that object:
package Child;
use Parent ();
our $SUPER = 'Parent';
use Carp;
sub new {
my ($class, $foo, #args) = #_;
bless {
parent => $SUPER->new(#args),
foo => $foo,
} => $class;
}
sub foo {
my ($self) = #_;
$self->{foo};
}
sub set_foo {
my ($self, $val) = #_;
$self->{foo} = $val;
}
# manually establish pseudo-inheritance
# return true if our class inherits a given package
sub isa {
my ($self, $class) = #_;
return !!1 if $class eq __PACKAGE__;
return +(ref $self ? $self->{parent} : $SUPER)->isa($class);
}
# return a coderef to that method, or false
sub can {
my ($self, $meth) = #_;
my %methods = (new => \&new, foo => \&foo, set_foo => \&set_foo, DESTROY => \&DESTROY);
if (my $code = $methods{$meth}) {
return $code;
}
# check parent
my $code = ( ref $self ? $self->{parent} : $SUPER)->can($meth);
return undef unless $code;
return sub {
my $self = shift;
unshift #_, ref $self ? $self->{parent} : $self;
goto &$code;
};
}
# write explicit destroy to satisfy autoload
sub DESTROY {
my ($self) = #_;
$self->{parent}->DESTROY if ref $self and $SUPER->can('DESTROY');
}
sub AUTOLOAD {
# fetch appropriate method coderef
my $meth = our $AUTOLOAD;
$meth =~ s/.*:://; # clean package name from name
my $code = $_[0]->can($meth);
$code or croak qq(Can't locate object method "$meth" via package "#{[__PACKAGE__]}");
goto &$code;
}
The ugly part is to fake methods defined in superclasses in the can code: We have to wrap the actual method inside a anonymous sub that unpacks our object to call the method on the proxied object. The gotos make our extra levels invisible to the called code, which is neccessary when somebody uses caller.
Most of this boilerplate proxying code can be abstracted into another module (and probably is, somewhere on CPAN).

Related

In Perl, can you subclass and hook all parent-class functions without `AUTOLOAD`?

I'm writing a subclass that encapsulates multiple objects of the parent class so I can call functions sort-of like a vector, something like this:
package OriginalClass;
sub new { return bless {bar => 123}, 'OriginalClass' }
sub foo { return shift->{bar}; }
1;
package NewClass;
use parent OriginalClass;
# Return a blessed arrayref of "OriginalClass" objects.
# new() would be called NewClass->new(OriginalClass->new(), ...)
sub new {
my $class = shift;
return bless \#_, 'NewClass';
}
# Vectorized foo(), returns a list of SUPER::foo() results:
sub foo
{
my $self = shift;
my #ret;
push #ret, $_->SUPER::foo() foreach #$self;
return #ret;
}
1;
I don't want to write a new vectorized function in NewClass for each function in OriginalClass, particularly for when OriginalClass adds new functions to be maintained (vectorized) in NewClass.
Question:
As I understand AUTOLOAD is slow, so is there a way to vectorize calls OriginalClass via something like NewClass without AUTOLOAD?
As I understand AUTOLOAD is slow
If AUTOLOAD generates the missing sub, then only the first call is "slow" since subsequent calls of the same method don't result in AUTOLOAD being called at all.
package NewClass;
use strict;
use warnings;
sub new {
my $class = shift;
return bless( \#_, $class );
}
sub AUTOLOAD {
my $method_name = our $AUTOLOAD =~ s/^.*:://sr;
my $method = sub {
my $self = shift;
return map { $_->$method_name( #_ ) } #$self;
};
{
no strict 'refs';
*$method_name = $method;
}
goto &$method;
}
1
Note that I didn't use parent and SUPER::. This isn't an inheritance relationship. And it would prevent AUTOLOAD from getting called since AUTOLOAD is only called when a method doesn't exist.
You can use Sub::Name to "name the sub" for better diagnostics.
use Sub::Name qw( subname );
my $method = subname $method_name => sub { ... };
But yes, AUTOLOAD can be avoided here, as long as you can get a list of the method names in advance.
package NewClass;
use strict;
use warnings;
sub new {
my $class = shift;
return bless( \#_, $class );
}
for my $method_name (qw( foo ... )) {
my $method = sub {
my $self = shift;
return map { $_->$method_name( #_ ) } #$self;
};
no strict 'refs';
*$method_name = $method;
}
1
The above uses a hardcoded list, but more dynamic solutions are possible. For example, the list could be obtained from inspecting the contents of the OriginalClass namespace for subs (filtering out new and anything else inappropriate such as names starting with _).
Module https://metacpan.org/pod/Array::Delegate could be helpful : it delegates method calls to an array of objects.

How to create (or not) class instance methods at construction time based on inputs?

How would I create my class such that some methods will exist in the instance only if certain values were passed to the constructor?
Perhaps a more generic way of asking is: How can I add a method to an existing class instance?
You can attach an anonymous sub to an object based on flags:
use strict;
use warnings;
package Object;
sub new {
my $class = shift;
my $self = bless {}, $class;
my %args = #_;
if ($args{method}) {
$self->{method} = sub { print "hello\n" }
}
return $self;
}
sub method {
my $self = shift;
if (not defined $self->{method}) {
warn "Not bound\n";
return;
}
$self->{method}->();
}
1;
to use:
use Object;
my $obj1 = Object->new(method=>1);
$obj1->method();
my $obj2 = Object->new();
$obj2->method();
You can extend this to a number of methods through the same interface.
You can use Moose to apply a role at runtime.
package My::Class;
use Moose;
has foo => ( isa => 'Str', is => 'ro', required => 1 );
sub BUILD {
my $self = shift;
if ($self->foo eq 'bar') {
My::Class::Role->meta->apply($self);
}
}
no Moose;
package My::Class::Role;
use Moose::Role;
sub frobnicate {
my $self = shift;
print "Frobnicated!\n";
}
no Moose;
my $something = My::Class->new( foo => 'bar' );
print $something, "\n";
$something->frobnicate;
my $something_else = My::Class->new( foo => 'baz' );
print $something_else, "\n";
$something_else->frobnicate;
Gives:
Moose::Meta::Class::__ANON__::SERIAL::1=HASH(0x2fd5a10)
Frobnicated!
My::Class=HASH(0x2fd2c08)
Can't locate object method "frobnicate" via package "My::Class" at testmoose.pl line 32.
use AUTOLOAD to define the function. As a example method foo is called if $self->{foo} exists
sub AUTOLOAD {
my $methodname = $AUTOLOAD;
if ($methodname eq "foo" && exists($_[0]->{foo})){
goto &fooimplementationsub;
}
return;
}
An alternative technique is to use globs to define a new method at runtime
*PACKAGE::method = sub {
#code here
};
This has the disadvantage that the method is now visible to all instances of the class so is not quite what you want.
A third and possibly more risky/inefficient method is to use string eval
eval <<EOF
sub foo {
#code here
};
EOF
Again this has the disadvantage that the method is now visible to all instances of the class so is not quite what you want.
Methods are just subroutines in a package, and a package is just a hash holding typeglobs. And hashes can be modified at runtime.
So you could, in theory, add or remove methods given values in a constructor.
package WeirdClass;
sub new {
my ($class, $name, $code) = #_;
if ($name) {
no strict;
*{__PACKAGE__ . "::$name"} = $code;
}
bless {} => $class;
}
And then use it like:
my $object = WeirdClass->new(foo => sub {say "foo"});
$object->foo(); # prints "foo\n";
However, this method is available for all objects of that class:
my $another_object = WeirdClass->new();
$another_object->foo; # works too.
Using autoload, one can mock arbitrary methods:
package BetterClass;
sub new {
my ($class, %args) = #_;
bless \%args => $class;
}
# destructor will be called at cleanup, catch with empty implementation
sub DESTROY {};
sub AUTOLOAD {
my $self = shift;
(my $method = our $AUTOLOAD) =~ s/.*://; # $AUTOLOAD is like "BetterClass::foo"
# check if method is allowed
die "forbidden method $method" unless $self->{can}{$method};
# mock implementations
given ($method) {
say "foo" when "foo";
say "bar" when "bar";
when ("add") {
my ($x, $y) = #_;
return $x + $y;
}
default { die "unknown method $method" }
}
}
Then:
my $o = BetterClass->new(can => { foo => 1, bar => 0});
$o->foo;
my $p = BetterClass->new(can => {bar => 1, add => 1});
$p->bar;
say $p->add(5, 6);
Of course, these techniques can be combined freely.
Edit: can()
To make the AUTOLOAD work with can, the protected methods should be moved into a data structure:
my %methods;
BEGIN {
%methods = (
foo => sub {say "foo"},
bar => sub {say "bar"},
add => sub {
my ($self, $x, $y) = #_;
$x + $y;
},
);
}
Then override the can method:
# save a reference to the origional `can` before we override
my $orig_can;
BEGIN{ $orig_can = __PACKAGE__->can("can") }
sub can {
my ($self, $meth) = #_;
# check if we have a special method
my $code = $methods{$meth} if ref $self and $self->{can}{$meth};
return $code if $code;
# check if we have a normal method
return $self->$orig_can($meth);
}
And AUTOLOAD would change to
my ($self) = #_; # do not `shift`
(my $method = our $AUTOLOAD) =~ s/.*://;
my $code = $self->can($method) or die "unknown method $method";
goto &$code; # special goto. This is a AUTOLOAD idiom, and avoids extra call stack frames
Don't do too much magic. I've gotten away from AUTOLOAD because it causes maintenance issues where mysterious methods suddenly appear and disappear.
One way to handle what you want is to define all the methods you need, and if a particular object is of the wrong type, simply cause that method to croak:
sub Foo {
my $self = shift;
my $parameter = shift;
if ( $self->Class_type ne "Foo" ) {
croak qq(Invalid method 'Foo' on object #{[ref $self]});
}
print "here be dragons\";
return "Method 'Foo' successfully called";
}
The above will not allow method Foo to be called unless the class type is Foo.
If your objects won't change (or you don't want them to change) once an object is created, you can define that object as a sub-class.
Before you bless a newly created object, check that special value and decide whether or not you need to create a specific sub-class instead.
package My_class;
sub new {
my $class = shift;
my $class_type = shift;
my $self = shift;
if ( $class_type eq "Foo" ) {
bless $self, "My_class::Foo";
}
else {
bless $self, $class;
}
package My_class::Foo;
use base qw(My_class);
sub Foo {
my $self = shift;
return "Foo Method successfully called!";
}
Notice that my class My_class::Foo is a sub-class of My_class via the use base pragma. That means all methods for My_class are valid with objects of My_class::Foo. However, only objects of My_class::Foo can call the Foo method.
When I create my object (via the new subroutine), I look at the $class_type parameter. If it's a type Foo, I bless the class as My_class::Foo.
Here's an example where I use sub-classes to do what you want.
Every object is a class type of Question. You can see my constructor on line 1129. I pass in a question type as one of the parameters to my constructor.
In line 1174 to 1176, I create my object, but then append the question type to the class, and then bless the question as that sub-class type. All of my subclasses are a type Question (see my use base qw(Question); below each package declaration. However, only questions of sub-class Question::Date and Question::Regex have a method Format. And, only objects of type Question::Words have a method Force.
Hope this helps.
None of the answers so far given actually handle the question actually asked.
Adding methods to an instance in Perl is not directly supported. Object instances are always instances of some class, and that class is the thing that actually has methods. You cannot add a method to a single instance of a class, without making that method also available on every other instance of the same class.
For your problem you have two basic solutions:
Provide the methods always, but test a flag to see whether the method should apply to the given instance or not. This is by far the simplest.
Bless each object into subclasses depending on the flags. Subclass the main class to provide those methods as appropriate.
If you truely want to add methods on individual instances, then what you'll have to do is arrange that every instance is a single instance of a newly-derived class for every object. This gets harder to arrange for, doubly-so if you want to avoid leaking memory and cleaning up the classes once the objects are DESTROYed. This would however allow truely per-instance methods.
Since it is highly unlikely you'll truely need this third option it is far better to go with one of the first.

How to check some value on every call to object's methods?

I'd like to check on every call to my object's methods some value (in this case: token's age). Is it possible to set it to all methods at once? Like in constructor? I have such simple constructor:
sub new {
my $class = shift;
my %args = #_;
my $self = {};
$self->{key} = $args{key};
bless($self, $class);
($self->{token}, $self->{token_start}) = $self->_get_authorized_token();
return $self;
}
And bunch of methods, which depends of tokens age, like this:
sub add_item {
my $self = shift;
my %args = #_;
...
}
I'd like to avoid including age-checking in every method, so i look for more general way to implement it. Has there some?
All I can think of is to hide all your 'real' methods - either in the classical way with a preceding underscore, or in a hash of subroutines - and use AUTOLOAD to direct the call properly.
The example below shos the idea
module MyClass.pm
package MyClass;
use strict;
use warnings;
sub new {
bless {}, __PACKAGE__;
}
sub _method1 {
print "In method1\n";
}
sub _method2 {
print "In method2\n";
}
sub AUTOLOAD {
our $AUTOLOAD;
my ($class, $method) = $AUTOLOAD =~ /(.+)::(.+)/;
return if $method eq 'DESTROY';
my $newmethod = "${class}::_$method";
unless (exists &$newmethod) {
die qq(Can't locate object method "$method" via package "$class");
}
print "Preprocessing...\n";
goto &$newmethod
}
1;
program
use strict;
use warnings;
use MyClass;
my $thing = MyClass->new;
$thing->method1;
$thing->method2;
$thing->method3;
output
Preprocessing...
In method1
Preprocessing...
In method2
Can't locate object method "method3" via package "MyClass" at MyClass.pm line 23.
See Class::Method::Modifiers or Class::Method::Modifiers::Fast module.
I honestly think that if you're doing OO in Perl and you want to deal with things like attributes, method modifiers and deferred resource loading without the boilerplate, it's worth investing in learning Moose. To illustrate, this is one way to write what you want using Moose:
use Moose;
has key => (isa => 'Str', is => 'ro');
has token => (isa => 'HashRef', is => 'ro', lazy_build => 1);
before [qw(add_item method2 method3)] => sub {
my $self = shift;
if (do something with $self->token) {
# return, die, etc.
}
};
sub _build_token {
my $self = shift;
my $key = $self->key;
return { token => 'foo', token_start => time };
}
These might be helpful:
Moose::Manual::MethodModifiers
Moose::Cookbook::Basics::BinaryTree_BuilderAndLazyBuild

Define the method in the constructor of class in perl

I am reading code snippets like below:
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (! $self->can($meth)) {
no strict "refs";
*{ $class . "::" . $meth } = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
In the foreach loop, it seems that it creates some methods according to the parameters. There are two lines which I don't understand.Could someone help me? What's the * and {} used for?
no strict "refs";
*{ $class . "::" . $meth }
Best Regards,
This creates a symbol table alias.
The right side contains a reference to a function, so Perl will alias it to the subroutine $meth in the package $class.
See Symbol Tables in perlmod.
As eugene y have already explained, those lines manipulate the symbol table. In practical terms, they do so in order to create read-only accessor methods in the class based on whatever arbitrary list of attributes get passed into the constructor:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.10.0;
package SomeClass;
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (!$self->can($meth)) {
no strict "refs";
*{$class . "::" . $meth} = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
package main;
my $foo = SomeClass->new({foo => 5}); # Creates SomeClass::foo
say $foo->foo; # 5
my $bar = SomeClass->new({foo => 3, bar => 7}); # Creates SomeClass::bar
say $bar->foo; # 3
say $bar->bar; # 7
say $foo->bar; # undef - ::bar was added to all instances of SomeClass
say $foo->baz; # Boom! No such method.
Personally, I think this is questionable OO practice (a class should generally have a known set of attributes instead of potentially adding new ones each time an instance is constructed), but that's what it does...

What should I do with an object that should no longer be used in Perl?

I am writing a class that is linked to an external resource. One of the methods is a delete method that destroys the external resource. No further method calls should be made on that object. I was thinking of setting a flag and die'ing inside of all of the methods if the flag is set, but is there a better, easier way? Something involving DESTROY maybe?
So far, I am really liking Axeman's suggestion, but using AUTOLOAD because I am too lazy to recreate all of the methods:
#!/usr/bin/perl
use strict;
use warnings;
my $er = ExternalResource->new;
$er->meth1;
$er->meth2;
$er->delete;
$er->meth1;
$er->meth2;
$er->undelete;
$er->meth1;
$er->meth2;
$er->delete;
$er->meth1;
$er->meth2;
$er->meth3;
package ExternalResource;
use strict;
use warnings;
sub new {
my $class = shift;
return bless {}, $class;
}
sub meth1 {
my $self = shift;
print "in meth1\n";
}
sub meth2 {
my $self = shift;
print "in meth2\n";
}
sub delete {
my $self = shift;
$self->{orig_class} = ref $self;
return bless $self, "ExternalResource::Dead";
}
package ExternalResource::Dead;
use strict;
use Carp;
our $AUTOLOAD;
BEGIN {
our %methods = map { $_ => 1 } qw/meth1 meth2 delete new/;
}
our %methods;
sub undelete {
my $self = shift;
#do whatever needs to be done to undelete resource
return bless $self, $self->{orig_class};
}
sub AUTOLOAD {
my $meth = (split /::/, $AUTOLOAD)[-1];
croak "$meth is not a method for this object"
unless $methods{$meth};
carp "can't call $meth on object because it has been deleted";
return 0;
}
Is there a problem with simply considering the object in an invalid state. If the users hang on to it, isn't that their problem?
Here are some considerations:
Have you already decided whether it's worth dying over?
Chances are that if you have a function that is encapsulated enough, you really don't want to have the users parse through your code. For that purpose, you probably wouldn't like to use what I call the Go-ahead-and-let-it-fail pattern. 'Can't call method "do_your_stuff" on an undefined value' probably won't work as well for encapsulation purposes. Unless you tell them "Hey you deleted the object!
Here are some suggestions:
You could rebless the object into a class whose only job is to indicate an invalid state. It has the same basic form, but all symbols in the table point to a sub that just says "Sorry can't do it, I've been shut down (You shut me down, remember?)."
You could undef $_[0] in the delete. Then they get a nice 'Can't call method "read_from_thing" on an undefined value' from a line in their code--provided that they aren't going through an elaborate decorating or delegation process. But as pointed out by chaos, this doesn't clear up more than one reference (as I've adapted by example code below to show).
Some proof of concept stuff:
use feature 'say';
package A;
sub speak { say 'Meow!'; }
sub done { undef $_[0]; }
package B;
sub new { return bless {}, shift; }
sub speak { say 'Ruff!' }
sub done { bless shift, 'A'; }
package main;
my $a = B->new();
my $b = $a;
$a->speak(); # Ruff!
$b->speak(); # Ruff!
$a->done();
$a->speak(); # Meow!
$b->speak(); # Meow! <- $b made the switch
$a->done();
$b->speak(); # Meow!
$a->speak(); # Can't call method "speak" on an undefined value at - line 28
Ideally, it should fall out of scope. If, for some reason, a proper scope can't be deliniated, and you're worried about references accidentally keeping the resource active, might consider weak references (Scalar::Util is core in at least 5.10).
You could make the users only get weakrefs to the object, with the single strong reference kept inside your module. Then when the resource is destroyed, delete the strong reference and poof, no more objects.
Following on from the comments in my first answer here is "one way" to amend an object behaviour with Moose.
{
package ExternalResource;
use Moose;
with 'DefaultState';
no Moose;
}
{
package DefaultState;
use Moose::Role;
sub meth1 {
my $self = shift;
print "in meth1\n";
}
sub meth2 {
my $self = shift;
print "in meth2\n";
}
no Moose::Role;
}
{
package DeletedState;
use Moose::Role;
sub meth1 { print "meth1 no longer available!\n" }
sub meth2 { print "meth2 no longer available!\n" }
no Moose::Role;
}
my $er = ExternalResource->new;
$er->meth1; # => "in meth1"
$er->meth2; # => "in meth2"
DeletedState->meta->apply( $er );
my $er2 = ExternalResource->new;
$er2->meth1; # => "in meth1" (role not applied to $er2 object)
$er->meth1; # => "meth1 no longer available!"
$er->meth2; # => "meth2 no longer available!"
DefaultState->meta->apply( $er );
$er2->meth1; # => "in meth1"
$er->meth1; # => "in meth1"
$er->meth2; # => "in meth2"
There are other ways to probably achieve what you after in Moose. However I do like this roles approach.
Certainly food for thought.
With Moose you can alter the class using its MOP underpinnings:
package ExternalResource;
use Moose;
use Carp;
sub meth1 {
my $self = shift;
print "in meth1\n";
}
sub meth2 {
my $self = shift;
print "in meth2\n";
}
sub delete {
my $self = shift;
my %copy; # keeps copy of original subref
my #methods = grep { $_ ne 'meta' } $self->meta->get_method_list;
for my $meth (#methods) {
$copy{ $meth } = \&$meth;
$self->meta->remove_method( $meth );
$self->meta->add_method( $meth => sub {
carp "can't call $meth on object because it has been deleted";
return 0;
});
}
$self->meta->add_method( undelete => sub {
my $self = shift;
for my $meth (#methods) {
$self->meta->remove_method( $meth );
$self->meta->add_method( $meth => $copy{ $meth } );
}
$self->meta->remove_method( 'undelete' );
});
}
Now all current and new instances of ExternalResource will reflect whatever the current state is.