开发者

perl subroutine reference

开发者 https://www.devze.com 2023-02-05 15:32 出处:网络
I have a set of fields with each field having different set of validation rules. I have placed the subroutine reference for validating a hash-ref.

I have a set of fields with each field having different set of validation rules.

I have placed the subroutine reference for validating a hash-ref.

Currently its in my constructor, but I want 开发者_开发技巧to take it out of my constructor in a private sub.

I have done it as below

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

$class = (ref($class)) ? ref $class : $class;
bless($self, $class);

$self->{Validations} = {
  Field1 => {name => sub{$self->checkField1(@_);},args => [qw(a b c)]}
  Field2 => {name => sub{$self->checkField2(@_);},args => {key1, val1}}
..
..
..
..
};

return $self;
}

Now I want to take out all this validation rules out of my constructor and want to do some thing like below, so that I have some better control over my validation rules based on types fields.(Say some rules are common in one set of fields and I can overwrite rules for other rules just by overwriting the values of fields.)

bless($self, $class);

  $self->{Validations} = $self->_getValidation($self->{type});

  return $self;
}
sub _getValidation{
     my ($self,$type) = @_;
     my $validation = {
     Field1  => {name => sub {$self->checkField1(@_);}, args => {key1 => val1}},};

     return $validation;
}

But I am getting Can't use string ("") as a subroutine ref while "strict refs" in use at... Can anybody tell me why is this behavior with sub ref. If I check my name key, its coming to be null or sub {DUMMY};


It looks to me like you are getting close to reinventing Moose poorly. Consider using Moose instead of building something similar, but less useful.

The error message means that you are passing in a string in a place where your code expects a code reference. Get a stack trace to figure out where the error is coming from.

You can do this by using Carp::Always, overriding the $SIG{__DIE__} handler to generate a stack trace, or inserting a Carp::confess into your code.

Here's a sigdie solution, stick this in your code where it will run before your module initialization:

$SIG{__DIE__} = sub { Carp::confess(@_) };

You may need to put it in a BEGIN block.

I'd really like to discourage you from taking this approach to building objects. You happily bless any random crap passed in to the constructor as part of your object! You blithely reach into your object internals. Field validation rules *do not belong in the constructor--they belong in the attribute mutators.

If you must use a DIY object, clean up your practices:

# Here's a bunch of validators.
# I set them up so that each attribute supports:
#   Multiple validators per attribute
#   Distinct error message per attribute
my %VALIDATORS = (

    some_attribute  => [
        [ sub { 'foo1' }, 'Foo 1 is bad thing' ],
        [ sub { 'foo2' }, 'Foo 2 is bad thing' ],
        [ sub { 'foo3' }, 'Foo 3 is bad thing' ],
    ],
    other_attribute => [ [ sub { 'bar' }, 'Bar is bad thing' ] ],

);


sub new {
    my $class = shift;  # Get the invocant
    my %args = @_;      # Get named arguments

    # Do NOT make this a clone method as well   

    my $self = {};
    bless $class, $self;

    # Initialize the object;
    for my $arg ( keys %args ) {

        # Make sure we have a sane error message on a bad argument.
        croak "Bogus argument $arg not allowed in $class\n"
            unless $class->can( $arg );

        $self->$arg( $args{$arg} );
    }

    return $self;
}

# Here's an example getter/setter method combined in one.
# You may prefer to separate get and set behavior.

sub some_attribute {
    my $self = shift;

    if( @_ ){
        my $val = shift;

        # Do any validation for the field
        $_->[0]->($val) or croak $_->[1]
            for @{ $VALIDATORS{some_attribute} || [] };

        $self->{some_attribute} = $val;
    }

    return $self->{some_attribute};

}

All this code is very nice, but you have to repeat your attribute code for every attribute. This means a lot of error-prone boilerplate code. You can get around this issue by learning to use closures or string eval to dynamically create your methods, or you can use one of Perl's many class generation libraries such as Class::Accessor, Class::Struct, Accessor::Tiny and so forth.

Or you can learn [Moose][3]. Moose is the new(ish) object library that has been taking over Perl OOP practice. It provides a powerful set of features and dramatically reduces boilerplate over classical Perl OOP:

use Moose;

type 'Foo'
    => as 'Int'
    => where {
        $_ > 23 and $_ < 42
    }
    => message 'Monkeys flew out my butt';

has 'some_attribute' => (
    is  => 'rw',
    isa => 'Foo',
);


I haven't read everything you had, but this struck me:

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

    $class = (ref($class)) ? ref $class : $class;
    bless($self, $class);

Normally, when you create a new object, the user doesn't pass $self as one of the objects. That's what you're creating.

You usually see something like this:

sub new {
    my $class = shift;   #Contains the class
    my %params = @_;     #What other parameters used

    my $self = {};       #You're creating the $self object as a reference to something
    foreach my $param (keys (%params)) {
       $self->{$param} = $params{$param};
    }

    bless ($self, $class)  #Class is provided. You don't have to check for it.
    return $self    #This is the object you created.
}

Now, $self doesn't have to be a reference to a hash as in the above example. It could be a reference to an array. Or maybe to a function. But, it's usually a reference. The main point, is that the user doesn't pass in $self because that's getting created by your new subroutine.

Nor, do you have to check the value of $class since that's given when the new subroutine is called.

If you want to do your verification in a private class (an excellent idea, by the way), you can do so after the bless:

sub new {
    my $class = shift;   #Contains the class
    my %params = @_;     #What other parameters used

    my $self = {};       #You're creating the $self object as a reference to something
    foreach my $param (keys (%params)) {
       $self->{$param} = $params{$param};
    }

    bless ($self, $class)  #Class is provided. You don't have to check for it.

    #Now you can run your verifications since you've blessed the object created
    if (not $self->_validate_parameters()) {
       croak qq(Invalid parameters passed in class $class);
    }
    return $self    #This is the object you created.
}
0

精彩评论

暂无评论...
验证码 换一张
取 消