如何初始化子类继承的属性?

Jim*_*ger 7 inheritance attributes raku

此代码按预期工作:

use v6.d;

class Foo {
    has $.name;

    submethod BUILD (:$!name = 'John') {};
}
my $f = Foo.new;
say $f;
# OUTPUT: Foo.new(name => "John")
Run Code Online (Sandbox Code Playgroud)

但是,当我添加:

class Bar is Foo {
    submethod BUILD (:$!name = 'Jane') {};
}
my $b = Bar.new;
say $b;
Run Code Online (Sandbox Code Playgroud)

我收到此错误消息:

===抱歉!=== 编译 scrap.raku 时
出错 属性$!name未在 Bar 类中
声明scratch.raku:14

如何在构建时分配默认值?

jub*_*us1 6

这是一种TWEAK改编自 brian d foy 的书“Learning Raku”(以前的“Learning Perl6”)第 12 章:“类”的方法:

use v6.d;

class Foo {
    has $!default-name = 'John';
    has $.custom-name  is rw;

    submethod TWEAK (:$custom-name) {
      self.custom-name = $custom-name // $!default-name;
      };
}

my $f = Foo.new;
say $f; 
put "This is class Foo with {$f.custom-name} as name.";


class Bar is Foo {}

my $b = Bar.new;
$b.custom-name = 'Jane';
say $b;
put "This is class Bar with {$b.custom-name} as name.";
Run Code Online (Sandbox Code Playgroud)

输出:

Foo.new(custom-name => "John")
This is class Foo with John as name.
Bar.new(custom-name => "Jane")
This is class Bar with Jane as name.
Run Code Online (Sandbox Code Playgroud)

当没有指定自定义名称时,上面的 Foo 类的“John”采用默认名称。在第二个示例中,Bar 类的“Jane”采用分配给它的自定义名称。

您可以修改class Bar为拥有自己的default-name和 自己的TWEAK子方法。class Foo那么原始方法和继承方法之间的唯一区别class Bar is Foo {}似乎是声明$.default-name为公共方法。

https://www.learningraku.com/2018/08/14/table-of-contents/
https://www.oreilly.com/library/view/learning-perl-6/9781491977675/


p6s*_*eve 6

这是根据您的评论编辑的版本。这样,子类的默认值就在子类中设置,但如果需要也可以显式设置。

use v6.d;

class Foo {
    has $.name = 'John'
}

class Bar is Foo {
    method new( *%h ) { 
        %h<name> //= 'Jane';
        nextwith( |%h )
    }   
}

Foo.new.say;                     # OUTPUT: Foo.new(name => "John")
Bar.new.say;                     # OUTPUT: Bar.new(name => "Jane")
Bar.new(name => 'Dora').say;     # OUTPUT: Bar.new(name => "Dora")
Run Code Online (Sandbox Code Playgroud)

由于我之前的版本依赖于 TWEAK,所以我认为尝试这种方式也会很有趣。

class Foo {
    has $!name;

    multi method name {              #getter
        $!name 
    }   
    multi method name( $value ) {    #setter
        $!name = $value 
    }   

    submethod TWEAK {
        $!name //= 'John'
    }   

    method gist(::T:) {              #captures type of its invocant
        "{::T.^name}.new(name => \"$!name\")"
    }   
}

class Bar is Foo {
    submethod TWEAK( :$name ) { 
        self.name: $name // 'Jane'
    }   
}

Foo.new.say;                     # OUTPUT: Foo.new(name => "John")
Bar.new.say;                     # OUTPUT: Bar.new(name => "Jane")
Bar.new(name => 'Dora').say;     # OUTPUT: Bar.new(name => "Dora")
Run Code Online (Sandbox Code Playgroud)

这有点棘手,因为公共属性简写has $.name;会自动生成(i)公共访问器 getter/setter 方法,以及(ii)用于轻松分配的代理,以及(iii)调整.gist以便通过 快速轻松地查看所有公共属性.say。但这些功能在施工完成之前还没有准备好。

因此,此示例必须具有具有has $!name;私有属性的显式 setter/getter 多重方法。

公共属性简写就像 OO 的训练轮,它提供了基本 OO 作为透明数据结构的简单使用,并且学习曲线较低(如 Python)。回避这个问题就像通过适当的封装将 raku 置于更正式的 OO 模式中一样。当你攀登 OO 山时,通过继承、角色、委托、信任等,raku 温和地鼓励你变得更加正式。

这里我们需要这样做,以获得$!name在构造过程中访问父类中属性的方法。

现在对于 Foo 的 TWEAK 子方法来说,通过分配给它的私有 $!name 来应用默认值已经很容易了。

现在 Bar 的 TWEAK 子方法可以使用 getter/setter,因为所有常规方法在 TWEAK 时都可用。(实际上,如果在此示例中使用 BUILD 而不是 TWEAK,您会得到相同的结果。)

其他一些调整 -

  • 说调用一个对象 - 所以如果你想显示它们,.gist我们需要显式地将私有属性放回自定义中.gist
  • 在这里,我使用::T类型捕获,这样我只需要做一次,并且相同的父方法适用于所有子类
  • 需要使用self.而不是$.在 TWEAK 时使用(所有$.虚拟方法机制都在之后出现),并且
  • 需要使用 setter 方法的方法语法self.name: 'value'(IMO 是一个更好的方法self.name('value')),因为公共 attr 代理不存在

编辑后,以下内容有点断章取义,但无论如何我都会离开这里。

神奇的是,特殊方法 new、BUILD 和 TWEAK 将仅根据其签名自动设置命名属性。

就是为什么我更喜欢使用 TWEAK 而不是 BUILD:

BUILD 可以设置属性,但它无权访问声明为其默认值的属性的内容,因为它们仅在以后应用。另一方面,TWEAK 在应用默认值后调用,因此会发现属性已初始化。因此它可以用来在对象构造后检查事物或修改属性。