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



代码正常运行:

use v6.d;
class Foo {
has $.name;
submethod BUILD (:$!name = 'John') {};
}
my $f = Foo.new;
say $f;
# OUTPUT: Foo.new(name => "John")
然而,当我添加:
class Bar is Foo {
submethod BUILD (:$!name = 'Jane') {};
}
my $b = Bar.new;
say $b;

我得到这个错误信息:

= = =对不起!===编译时出错。属性$!name未在类Bar
atscratch.raku:14中声明

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

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

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")

由于我之前的版本依赖于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")

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

因此,这个例子必须有显式的setter/getter多方法,has $!name;作为私有属性。

公共属性简写就像OO的训练轮,它提供了简单的基本OO使用,作为透明的数据结构,具有较低的学习曲线(如Python)。回避这一点就像将raku放入更正式的OO模式并进行适当的封装。随着继承、角色、委托、信任等的不断深入,raku逐渐鼓励您变得更加正式。

这里我们需要这样做,以便在构造期间访问父类中的$!name属性。

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

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

其他一些调整-

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

在编辑之后,下面的内容有点脱离上下文,但我还是要离开这里。

神奇之处在于特殊的方法new, BUILD和TWEAK将根据它们的签名自动设置命名属性。

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

BUILD可以设置属性,但是它没有访问属性的权限声明为其默认属性的内容应用。另一方面,在默认值之后调用TWEAK已应用,因此将发现属性已初始化。所以它可以用来检查事物或修改对象后的属性吗建设。

这里是一个使用TWEAK的方法,改编自brian d foy的书,"Learning Raku"(原"Learning perl "),第12章:"Classes":

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.";

输出:

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.

类Foo的"John"当没有分配自定义名称时,使用默认名称。在第二个例子中Bar's "Jane"接受分配给它的自定义名称。

您可以修改class Bar,使其拥有自己的default-nameTWEAK子方法。那么,原始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/

我认为这接近你想要做的,尽管它需要使字段名可变,这里声明为"is rw":

class Foo {
has $.name  is rw = 'John';  # easy way to specify a default
}
class Bar is Foo {
submethod TWEAK (:$name) {
self.name = $name // 'Jane';
};
}
my $f = Foo.new;
say $f;   # Foo.new(name => "John")
my $b = Bar.new;
say $b;  # Bar.new(name => "Jane")

这会给你一个带有不同默认值的子类,虽然你可能认为这是你可以改变的缺点对象创建后的名称:

$b.name = 'Janezilla'; 
say $b;  # Bar.new(name => "Janezilla")

还有另一种选择——不需要在Foo中创建属性——是使用组合,而不是将继承与handles特性结合使用。

use v6.d;
class Foo {
has $.name = 'John' ;
}
class Bar {
has Foo $!foo handles 'name' ;
submethod TWEAK( :$name = 'Jane' ) {
$!foo .= new: :$name
}
}
say Foo.new.name ;                    # "John"
say Bar.new.name ;                    # "Jane"
say Bar.new(name => "Terrie").name    # "Terrie"
可以说,handles子句更清楚地声明了你的意图——将Foo中的'name'属性作为你自己的。有人会说,在任何情况下,复合都是比继承更好的方法——但显然,这将取决于你在更广泛的上下文中做这件事。

相关内容

  • 没有找到相关文章

最新更新