在perl中向现有对象添加新方法

时间:2015-02-06 19:34:58

标签: perl oop

我有这个perl对象。在实例化对象之后,我正在尝试在loader方法中向对象添加一个新方法,然后可以稍后调用它。

我尝试了一大堆没有用的东西。例子包括:

sub loader {
    my ($self) = @_;

    sub add_me {
        my ($self, $rec) = @_

        warn "yayyyyyy";
        return $rec;
    }

    #here are the things I've tried that dont work:
    # &{$self->{add_me}} = \&add_me;
    # \&{$self->{add_me}} = \&add_me;
    # assuming the class definition is in Holder::Class try to add it to symblol table
    # *{Holder::Class::add_me} = \&add_me;

}

修改

我需要这样做的原因是我在我的代码中添加了一个钩子,我的软件用户将能够注入自己的子来编辑数据结构。

为此,他们将能够编辑一个只包含一个sub的辅助文件,并获取传入的数据结构,如下所示:

sub inject_a_sub {
    my ($self, $rec) = @_;

    #do stuff to $rec

    return $rec;
}

然后在我的原始对象实例化之后,我检查上面提到的文件是否存在,如果是这样,请阅读其内容并评估它们。最后,我想制作eval'd代码,它只是一个sub,一个我的对象的方法。确切地说,我的对象已经继承了一个名为do_something的方法,并且我希望通过eval使子读取覆盖继承的do_something方法,以便在调用时从外部文件运行sub

这是一个奇怪的问题:/

它伤害了我:(

Obi wan kenobi你是我唯一的希望!

干杯!

1 个答案:

答案 0 :(得分:9)

如果您只想将功能附加到特定对象,并且不需要继承,则可以在对象中存储代码引用并调用它。

# Store the code in the object, putting it in its own
# nested hash to reduce the chance of collisions.
$obj->{__actions}{something} = sub { ... };

# Run the code
my @stuff = $obj->{__actions}{something}->(@args);

问题是,您需要检查$obj->{__actions}{something}是否包含代码参考。我建议的是围绕这个过程包装一个方法。

sub add_action {
    my($self, $action, $code) = @_;

    $self->{__actions}{$action} = $code;

    return;
}

sub take_action {
    my($self, $action, $args) = @_;

    my $code = $self->{__actions}{$action};
    return if !$code or ref $code ne 'CODE';

    return $code->(@$args);
}

$obj->add_action( "something", sub { ... } );
$obj->take_action( "something", \@args );

如果您已经知道要将方法注入的类名,请正常编写子例程,但使用完全限定名称。

sub Some::Class::new_method {
    my $self = shift;

    ...
}

请注意,该子例程中的任何全局变量都将位于周围的包中,而在Some :: Class中不是。如果您想要持久变量,请在子例程内使用state或在子例程外使用my


如果你在编译时不知道这个名字,你就必须将子程序注入到符号表中,所以你接近最后一个。

sub inject_method {
    my($object, $method_name, $code_ref) = @_;

    # Get the class of the object
    my $class = ref $object;

    {
        # We need to use symbolic references.
        no strict 'refs';

        # Shove the code reference into the class' symbol table.
        *{$class.'::'.$method_name} = $code_ref;
    }

    return;
}

inject_method($obj, "new_method", sub { ... });

Perl中的方法与类相关联,而不是与对象相关联。为了将方法分配给单个对象,您必须将该对象放入其自己的类中。与上面类似,但您必须为每个实例创建一个子类。

my $instance_class = "_SPECIAL_INSTANCE_CLASS_";
my $instance_class_increment = "AAAAAAAAAAAAAAAAAA";
sub inject_method_into_instance {
    my($object, $method_name, $code_ref) = @_;

    # Get the class of the object
    my $old_class = ref $object;

    # Get the special instance class and increment it.
    # Yes, incrementing works on strings.
    my $new_class = $instance_class . '::' . $instance_class_increment++;

    {
        # We need to use symbolic references.
        no strict 'refs';

        # Create its own subclass
        @{$new_class.'::ISA'} = ($old_class);

        # Shove the code reference into the class' symbol table.
        *{$new_class.'::'.$method_name} = $code_ref;

        # Rebless the object to its own subclass
        bless $object, $new_class;
    }

    return;
}

我省略了代码以检查实例是否已经通过检查其类是否匹配/^${instance_class}::/来进行此处理。我把它留作练习给你。为每个对象创建一个新类并不便宜,并且会耗费内存。


有充分的理由这样做,但它们是例外。你应该真的,真的质疑你是否应该这样做monkey patching。通常,应避免使用action at a distance

您可以使用子类,委托或角色完成相同的事情吗?

已经存在Perl OO系统,它将为您提供更多功能。你应该使用一个。 Moose,Moo(通过Role::Tiny)和Mouse都可以为实例添加角色。