Perl的“bless”究竟有什么作用?

nr7wwzry  于 2022-11-15  发布在  Perl
关注(0)|答案(8)|浏览(180)

我了解到在Perl中,在类的“new”方法中使用了“bless”关键字:

sub new {
    my $self = bless { };
    return $self;
}

但是“bless”到底对那个哈希引用做了什么呢?

piah890a

piah890a1#

通常,bless将对象与类关联。

package MyClass;
my $object = { };
bless $object, "MyClass";

现在,当您在$object上调用一个方法时,Perl知道要在哪个包中搜索该方法。
如果省略了第二个参数(如示例中所示),则使用当前包/类。
为了清楚起见,您的示例可能编写如下:

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
}

编辑:请参阅kixx的好答案,了解更多细节。

baubqpgj

baubqpgj2#

bless将引用与包关联。
引用的对象并不重要,它可以是散列(最常见的情况)、数组(不太常见)、标量(通常表示由内而外的对象)、正则表达式、子例程或TYPEGLOB(参见Object Oriented Perl: A Comprehensive Guide to Concepts and Programming Techniques by Damian Conway一书中的有用示例),甚至是文件或目录句柄的引用(最不常见的情况)。
bless-ing的作用是,它允许您将特殊的语法应用于受祝福的引用。
例如,如果一个受祝福的引用存储在$obj中(通过bless与包“Class”关联),则$obj->foo(@args)将调用子例程foo,并传递引用$obj作为第一个自变量,后面是其余自变量(@args)。子例程应该在包“Class”中定义。如果包“Class”中没有子例程foo,将搜索其他包的列表(取自包“Class”中的数组@ISA),并且将调用找到的第一个子例程foo

f5emj3cl

f5emj3cl3#

简短版本:它将该哈希值标记为附加到当前包名称空间(以便该包提供其类实现)。

frebpwbc

frebpwbc4#

这个函数告诉REF引用的实体,它现在是CLASSNAME包中的一个对象,如果省略了CLASSNAME,则是当前包中的一个对象。建议使用双参数形式的bless。

示例

bless REF, CLASSNAME
bless REF

传回值

此函数返回对已分配到CLASSNAME中的对象的引用。

示例

下面的示例代码显示了它的基本用法,对象引用是通过将一个引用赋给包的类来创建的−

#!/usr/bin/perl

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # Print all the values just for clarification.
    print "First Name is $self->{_firstName}\n";
    print "Last Name is $self->{_lastName}\n";
    print "SSN is $self->{_ssn}\n";
    bless $self, $class;
    return $self;
}
pcww981p

pcww981p5#

在内部明确区分bless-艾德referent的是,用于引用的SV(存储在标量中)获取一个额外的FLAGS值(OBJECT),以及一个携带包名的STASH(还有一些其他区别)

perl -MDevel::Peek -wE'
    package Pack  { sub func { return { a=>1 } } }; 
    package Class { sub new  { return bless { A=>10 } } }; 
    $vp  = Pack::func(); print Dump $vp;   say"---"; 
    $obj = Class->new;   print Dump $obj'

打印,抑制相同(与此无关)的部分

SV = IV(0x12d5530) at 0x12d5540
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x12a5a68
  SV = PVHV(0x12ab980) at 0x12a5a68
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ...
      SV = IV(0x12a5ce0) at 0x12a5cf0
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1
---
SV = IV(0x12cb8b8) at 0x12cb8c8
  REFCNT = 1
  FLAGS = (PADMY,ROK)
  RV = 0x12c26b0
  SV = PVHV(0x12aba00) at 0x12c26b0
    REFCNT = 1
    FLAGS = (OBJECT,SHAREKEYS)         <--
    STASH = 0x12d5300   "Class"        <--
    ...
      SV = IV(0x12c26b8) at 0x12c26c8
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 10

这样,解释器就知道

  • 这是一个对象,
  • 它属于哪个包

并且这告知其用途。
例如,当遇到对该变量的解引用($obj->name)时,在包(或层次结构)中查找具有该名称的子对象,将该对象作为第一个参数传递,等等。

vuv7lop3

vuv7lop36#

我将尝试在这里提供一个答案,因为在我最初写这篇文章的时候,这里的答案并不适合我(警告,这个答案的结构相当糟糕,请随意跳过对你不是特别有用的部分)。
Perl的bless函数将指定的引用与包名字符串相关联,并使blessed引用的箭头操作符在与该引用相关联的包中查找方法,如果没有找到,则使用@ISA数组(如果有)继续查找(这超出了本文的范围)。
我们为什么需要这个?
让我们开始用JavaScript表达一个示例:

(() => {
    //'use strict'; // uncomment to fix the bug mentioned below.

    class Animal {
        constructor(args) {
            console.log(this);
            this.name = args.name;
            this.sound = args.sound;
        }
    }

    /* This is left for historical reasons, 
     *    modern JavaScript engines no longer allow you to
     *    call class constructors without using new.
     * 
     * var animal = Animal({
     *     'name': 'Jeff',
     *     'sound': 'bark'
     * }); 
     * console.log(animal.name + ', ' + animal.sound); // seems good
     * console.log(window.name); // my window's name is Jeff?
     */

    // as of recently, Animal constructor cannot be called without using the new operator.
    var animal = new Animal({
        'name': 'Jeff',   
        'sound': 'bark'
    });

    console.log(animal.name + ', ' + animal.sound); // still fine.
    console.log("window's name: " + window.name); // undefined
})();

现在让我们看看这个类构造的去糖版本:

(() => {
    // 'use strict'; // uncomment to fix bug.

    var Animal = function(args) {
        this.name = args.name;
        this.sound = args.sound;
        return this; // implicit context hashmap
    };
    
    /** 
     *  The bug left for historical reasons,
     *      should still work now in modern web developer consoles.
     *      
     *  var animal = Animal({
     *      'name': 'Jeff',
     *      'sound': 'Bark'
     *  });
     *  console.log(animal.name + ', ' + animal.sound); // seems good
     *  console.log("The window's name is: " + window.name); // my window's name is Jeff?
     */
  
    // the new operator causes the "this" inside methods to refer to the animal
    // rather than the global scope, so the bug mentioned above does not occur.
    var animal = new Animal({
        'name': 'Jeff',
        'sound': 'bark'
    });
    console.log(animal.sound);    
    console.log(window.name); // the name has not been changed by the constructor.
})();

Animal的构造函数接受一个Object的属性,并返回一个具有这些属性的Animal,或者,如果您忘记输入new关键字,它将返回整个全局上下文(即浏览器开发者控制台中的window)。
Perl没有“this”、“new”或“class”,但它仍然可以有一个行为类似的函数。我们不会有构造函数或原型,但我们将能够创建新的动物并修改它们的个体属性。

# immediatly invoked subroutine execution(IIFE).
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();

现在,我们有一个问题:如果我们希望动物自己发出声音,而不是直接打印它们的声音,也就是说,我们需要一个函数performSound来打印动物自己的声音。
一种方法是为Animal的每个示例提供其自己的performSound子例程引用。

# self contained scope 
(sub {
    my $Animal = (sub {
        $name = $_[0]{'name'};
        $sound = $_[0]{'sound'};
    
        return {
            'name' => $name,
            'sound' => $sound,
            'performSound' => sub {
                print $sound . "\n";
            }
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    $animal->{'performSound'}();
})->();

这通常不是我们想要的,因为performSound是作为一个全新的子例程引用给每一个被构造的动物的。构造10000个动物可能会分配10000个performSound子例程。我们希望有一个子例程performSound供所有Animal示例使用,这些示例查找自己的声音并打印出来。

(() => {
    'use strict';

    /* a function that creates an Animal constructor which can be used to create animals */
    var Animal = (() => {
        /* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
        var InnerAnimal = function(args) {
            this.name = args.name;
            this.sound = args.sound;
        };
        /* defined once and all animals use the same single function call */
        InnerAnimal.prototype.performSound = function() {
            console.log(this.name);
        };
        
        return InnerAnimal;
    })();
 
    var animal = new Animal({
        'sound': 'bark',
        'name': 'Jeff'
    });
    animal.performSound(); // Jeff
})();

这里是与Perl相似的地方。
JavaScript的new运算符不是可选的,如果没有它,对象方法内部的“this”会污染全局作用域:

(() => {
    // uncommenting this prevents unintentional
    //     contamination of the global scope, and throws a TypeError instead.
    // 'use strict'; 

    var Person = function() {
        this.name = "Sam";
    };
//    var wrong = Person(); // oops! we have overwritten window.name or global.main.
//    console.log(window.name); // my window's name is Sam?
    var correct = new Person; // person's name is actually stored in the person now.    
})();

我们希望每个Animal都有一个函数,用于查找该动物自己的声音,而不是在构造时硬编码。
祝福让我们可以使用包的子例程,而不必为每个对象附加一个子例程ref,它还使ref引用更有意义的包名(如Animal)作为对象的名称,而不是无聊的HASH或任何其他你选择祝福的引用对象:

package Animal;
sub new {
    my $packageRef = $_[0];
    my $name = $_[1]->{'name'};
    my $sound = $_[1]->{'sound'};

    my $this = {
        'name' => $name,
        'sound' => $sound
    };   
    
    bless($this, $packageRef);
    return $this;
}

# all animals use the same performSound to look up their sound.
sub performSound {
    my $this = shift;
    my $sound = $this->{'sound'};
    print $sound . "\n";
}

package main;
my $animal = Animal->new({
    'name' => 'Cat',
    'sound' => 'meow'
});

print("The animal's ref is: " . ref($animal) . "\n");
$animal->performSound();

摘要/TL; DR

  1. Perl没有“this”,“class”,也没有“new”。
    1.将对象祝福给封装,可让该对象指涉封装。
    1.使用箭头操作符来调用一个受祝福的引用对象($blessedObject->method(...arguments))的方法通常与调用Package::method($blessedObject, ...arguments)相同,但是如果没有找到方法,它将继续使用包的@ISA来查找,这超出了本文的范围。
    1.实际上,您可以在运行时创建新类,只要您违反严格的'refs'或使用eval,下面是如何实现的演示:
#!/usr/bin/perl

use warnings;
use strict;

print('Enter the name for the class(eg Greeter): $ ');
my $class_name = <>;
chomp $class_name;

print('Enter the name of the method(eg greet): $ ');
my $method_name = <>;
chomp $method_name;

no strict 'refs';
# The line below violates strict 'refs'.
*{$class_name . '::new'} = sub {
    my $self = bless {}, $_[0];
    return $self;
}; 
use strict 'refs';

no strict 'refs';
# The line below violates strict 'refs'
*{$class_name . '::' . $method_name} = sub {
    print("Hello, World!\n");
};
use strict 'refs';

my $instance = ($class_name)->new();
$instance->$method_name();

为什么这么混乱?
bless之所以令人困惑的一个原因是,实际上有三种调用包的方法
1.通过A::a(),作为包子程序。
1.通过A->a(),作为一个包子例程,但它会将__PACKAGE__作为第一个参数在其他参数之前隐式传递。
1.通过$a->a()$a的祝福变成A
下面的代码对此进行了说明:

# | Illustrates catching 3 distinct ways of calling a package's member.
package Test;

sub new {        
    return bless {}, __PACKAGE__;
}

sub runTest {
    if (ref($_[0]) eq __PACKAGE__) {
        # | $_[0] is the blessed reference.
        # | Despite being called with "->", $_[1] is NOT "Test"(__PACKAGE__).
        print("Test::runTest was called through a blessed reference call(\$instance->runTest().\n");
    } elsif ($_[0] eq __PACKAGE__) {
        # | $_[0] is "Test"(__PACKAGE__), but we can't determine for sure whether it was -> or ::.
        print("Test::runTest was called through Test->runTest() or through Test::runTest() with 'Test' as the first argument.\n");
    } else {
        # | $_[0] is neither a blessed reference nor "Test"(__PACKAGE__), so it can't be an arrow call.
        print "Test::runTest was called through Test::runTest()\n";
    }
}

package main;

my $test = Test->new();
$test->runTest();
Test->runTest();
Test::runTest();
Test::runTest('Test'); # <- Same as "Test->runTest();"
Test::runTest($test); # <- Same as "$test->runTest();"

另一个原因是,与JavaScript不同,Python可以有多个具有不同名称但定义/方法/属性不同的类,Perl类有唯一的(ref $obj),因为在任何给定时间只能有一个具有特定名称的包,并且@伊萨需要一点时间来适应。
我的最后一个原因是,包不如其他脚本语言中的类那样有形,在其他脚本语言中,您可以通过赋值运算符将对类本身的引用存储在变量中,而在Perl中,您不仅不能存储对类的引用(您只能通过名称字符串引用软件包),但是试图通过一个存储在变量中的名字(例如String[$method])来引用一个包,而不违反严格的'refs'或使用eval似乎是不可能的。
无论如何,希望有人会发现这个职位有用。
注:这是一个相当老的尝试在一个答案,我已经试图清理数量的天真和尴尬/无意义/分散注意力的陈述,并添加更多有用的例子,以帮助理解这个概念,但它仍然是远离我希望它是(它仍然是相当令人沮丧的重读)。我离开它,因为我仍然相信它可能是有用的人。
请把它与盐增益,我道歉的任何头痛所呼吁的布局不佳的结构的答案。

e37o9pze

e37o9pze7#

我遵循这一思想来指导面向对象Perl的开发。
最好将任何数据结构引用与类相关联。考虑到Perl如何创建继承结构(在一种树中),很容易利用对象模型来创建用于组合的对象。
对于这个我们称为对象的关联,要开发时始终牢记对象的内部状态和类的行为是分离的.并且你可以祝福/允许任何数据引用使用任何包/类的行为.因为包可以理解对象的“情绪”状态.

tcomlyy6

tcomlyy68#

例如,如果你确信任何Bug对象都将是一个幸运的散列,你可以(终于!)在Bug::print_me方法中填充缺失的代码:

package Bug;
 sub print_me
 {
     my ($self) = @_;
     print "ID: $self->{id}\n";
     print "$self->{descr}\n";
     print "(Note: problem is fatal)\n" if $self->{type} eq "fatal";
 }

现在,每当print_me方法被调用时,通过引用任何一个被祝福到Bug类中的哈希值,$self变量提取作为第一个参数传递的引用,然后print语句访问被祝福的哈希值的各个条目。

相关问题