声明
本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。
第十三章. 角色
角色是 mixins,可以增强你的类,就像它们的内容被定义在类里一样。一旦定义,它们的源实际上会被遗忘(与父类不同)。你可以使用角色来更改类,从现有类创建新类,以及增强单个对象。它们比继承更灵活,通常是更好的解决方案。角色用于代码重用,而类用于管理对象。
给类添加行为
构造一个空的 Butterfly
类。即使没有属性接收参数的值,你也可以为 .new
提供参数:
class Butterfly {}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
现在给你的蝴蝶命名。名字应该是 Butterfly
类的一部分吗?名字不是对象. Hamadryas guatemalena 是蝴蝶的名字。 Guatemalan Cracker,Calicó 和 Soñadoracomún 也是蝴蝶的名字。这些都是同一只蝴蝶的名字。
注意
最终,你编写的代码必须在该语言框架内运行。语法有时会让你在认知上将事物分开。
A name is not a more specific version of something the class already does and it’s not limited to butterflies or butterfly-like things. Many dissimilar things can have a common name—animals, cars, food. Not only that, but different people, cultures, or even sections of your office may choose different names. This fact does not define your thingy or its behavior. It’s not something that makes a butterfly what it is.
名字不是该类已经做过的更具体的版本,它不仅限于蝴蝶或蝴蝶般的东西。许多不同的东西可以有一个共同的名字 - 动物,汽车,食物。不仅如此,不同的人,文化,甚至办公部门都可以选择不同的名字。这个事实并没有定义你的东西或它的行为。这不是让蝴蝶成为现实的东西。
Create a role that contains everything you need for a common name. Everything about a name (and nothing else!) can show up in that role. The role doesn’t care what sort of thingy uses it, whether that’s a butterfly, a car, or a pizza. Declare it with role
just as you would a class:
创建一个包含公共名字所需内容的角色。关于名字的所有内容(没有别的!)都可以显示在该角色中。这个角色并不关心什么样的东西使用它,无论是蝴蝶,汽车还是披萨。用 role
声明一个角色就像声明类一样:
role CommonName {
has $.common-name is rw = 'An unnamed thing';
}
In fact, a role can act just like a class. You can make an object from a role. This puns the role into a class:
事实上,角色就像类一样。你可以从角色创建对象。这个角色变成了一个类:
role CommonName {
has $.common-name is rw = 'An unnamed thing';
}
my $name = CommonName.new: :common-name('Perly Cracker');
put $name.common-name; # Perly Cracker
Apply a role to a class with does
after the class name in the same way you used is
for inheritance:
在类名之后使用 does
以类似于继承使用 is
的方式将角色应用于类:
class Butterfly does CommonName {};
Every Butterfly
object now has a $.common-name
attribute and .new
now sets the :common-name
using either the default or the name you provide:
每个 Butterfly
对象现在都有一个 $.common-name
属性,而 .new
现在使用默认名称或你提供的名称设置 :common-name
:
my $unnamed-butterfly = Butterfly.new;
put $unnamed-butterfly.common-name; # An unnamed thing
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
You can use the same role for something completely different. An SSL certificate has a common name, although its semantic meaning is different:
对于完全不同的东西,你可以使用相同的角色。 SSL 证书具有常用名,但其语义含义不同:
class SSLCertificate does CommonName {}
Butterflies and SSL certificates are completely different things and it wouldn’t make sense for them to inherit from the same thing. However, they can use the same role.
蝴蝶和 SSL 证书是完全不同的东西,它们从同一个东西继承是没有意义的。但是,他们可以使用相同的角色。
EXERCISE 13.1Create a ScientificName
role that adds an attribute to store a Str
for the scientific name. Apply that role to Butterfly
, create an object, and output the scientific name.
练习13.1 创建一个 ScientificName
角色,添加一个属性来存储学名的字符串
。将该角色应用于 Butterfly
,创建对象并输出学名。
Applying Multiple Roles
You can give a butterfly a scientific name as well as a common name by creating a different role for that. This one has several attributes:
你可以通过为蝴蝶创建不同的角色,为蝴蝶提供学名和常用名。这只蝴蝶有几个属性:
role ScientificName {
has $.kingdom is rw;
has $.phylum is rw;
has $.class is rw;
has $.order is rw;
has $.family is rw;
has $.genus is rw;
has $.species is rw;
}
You can replace the CommonName
role with ScientificName
and things work as before:
你可以使用 ScientificName
替换 CommonName
角色,并且事情像以前一样工作:
class Butterfly does ScientificName {};
my $butterfly = Butterfly.new: :genus('Hamadryas');
put $butterfly.genus; # Hamadryas;
Multiple does
expressions apply multiple roles:
多个 does
表达式应用多个角色:
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:common-name('Perly Cracker')
;
put $butterfly.genus;
put $butterfly.common-name;
Each role inserts its code into Butterfly
so it can respond to methods from either source:
每个角色都将其代码插入 Butterfly
中,以便它可以响应来自任何一个源的方法:
Hamadryas
Perly Cracker
EXERCISE 13.2Create a role Lepidoptera
to represent butterflies. Fill in everything from kingdom Animalia, phylum Athropoda, class Insecta, and order Lepidoptera. Allow the role to change the family, genus, and species. Use that role in your own Butterfly
class. After you get that working add the CommonName
role.
练习13.2 创建一个 Lepidoptera
代表蝴蝶的角色。填写动物界,动物门,昆虫纲和鳞翅目的一切。允许角色改变族,属和物种。在你自己的 Butterfly
类中使用该角色。完成后,添加 CommonName
角色。
Methods in Roles
You can define methods in roles too. Give the ScientificName
role a .gist
method to create your own human-readable text version of the object:
你也可以在角色中定义方法。为 Scientific Name
角色提供一个 .gist
方法,以创建自己的对象的人类可读文本版本:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
}
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:common-name('Perly Cracker')
;
put $butterfly.genus;
put $butterfly.common-name;
put $butterfly.gist;
EXERCISE 13.3Update your Lepidoptera
role to have a binomial-name
method that returns a Str
that combines the genus and species of the butterfly (in biospeak that’s the “binomial name”).
练习13.3 更新你的 Lepidoptera
角色以拥有 binomial-name
方法,它返回一个结合蝴蝶属和种类的字符串
(在biospeak中是“二项式名称”)。
To reuse these roles you want to make them available for any code to find and load. You can store roles by themselves in files just as you can with classes. Load them with use
and they are available in that scope.
要重用这些角色,你需要使它们可供任何代码查找和加载。你可以像在类中一样将文件存储在文件中。使用 use
加载它们,它们在该范围内可用。
EXERCISE 13.4Separate the Lepidoptera
and CommonName
roles and the Butterfly
class into their own files. Load those files into your program where you create your Butterfly
object. Make this program work:use Butterfly; my $butterfly = Butterfly.new: :family( 'Nymphalidae' ), :genus( 'Hamadryas' ), :species( 'perlicus' ), ; put $butterfly.binomial-name;
练习13.4 将 Lepidoptera
和 CommonName
角色还有 Butterfly
类分开到它们自己的文件中。在你要创建 Butterfly
的地方加载这些文件到你的程序中。使这个程序能工作:
use Butterfly;
my $butterfly = Butterfly.new:
:family( 'Nymphalidae' ),
:genus( 'Hamadryas' ),
:species( 'perlicus' ),
;
put $butterfly.binomial-name;
De-Conflicting Roles
If two roles try to insert the same names you may have to do extra work. Suppose that both ScientificName
and CommonName
had a .gist
method:
如果两个角色尝试插入相同的名称,则可能需要执行额外的工作。假设 ScientificName
和 CommonName
都有.gist方法:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
method gist { "Common name: $.common-name" }
}
class Butterfly does ScientificName does CommonName {};
The .gist
method has an explicit signature and isn’t marked with multi
. When you try to compile this you get an error telling you that two roles tried to insert the same method:
.gist
方法具有显式签名,并且未标记为 multi
。当你尝试编译它时,你会收到一个错误,告诉你两个角色尝试插入相同的方法:
Method 'gist' must be resolved by class Butterfly because
it exists in multiple roles (CommonName, ScientificName)
You can add a .gist
method to Butterfly
. Neither role replaces a method already in the class:
你可以向 Butterfly
添加 .gist
方法。这两个角色都不替换类中已有的方法:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
method gist { "Common name: $.common-name" }
}
class Butterfly does ScientificName does CommonName {
method gist {
join "\n",
join( ' > ', $.kingdom, $.genus ),
"Common name: $.common-name";
}
};
Or if you want both methods from the roles you can distinguish them with different signatures (and use multi
). Their role names as a type might do:
或者,如果你想要角色中的两种方法,则可以使用不同的签名区分它们(并使用 multi
)。他们的角色名称作为类型可能是:
role ScientificName {
...; # all the attributes specified earlier
multi method gist ( ScientificName ) {
"$.genus $.species";
}
}
role CommonName {
has $.common-name is rw;
multi method gist ( CommonName ) {
"Common name: $.common-name";
}
}
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas' ),
:species('perlicus'),
:common-name( 'Perly Cracker' ),
;
put '1. ', $butterfly.gist( CommonName );
put '2. ', $butterfly.gist( ScientificName );
This way you get both methods:
这样你就得到了两种方法:
1. Common name: Perly Cracker
2. Hamadryas perlicus
You can have the same method in the Butterfly
class as long as you declare it with multi
and give it a unique signature:
你可以在 Butterfly
类中使用相同的方法,只要用 multi
声明它并给它一个唯一的签名:
class Butterfly does ScientificName does CommonName {
multi method gist {
join "\n", map { self.gist: $_ },
( ScientificName, CommonName );
}
};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:species('perlicus'),
:common-name('Perly Cracker')
;
put '1. ', $butterfly.gist( CommonName );
put '2. ', $butterfly.gist( ScientificName );
put '3. ', $butterfly.gist;
Your output shows all three and you can pick whichever you like:
你的输出显示全部三个,你可以选择你喜欢的任何一个:
1. Common name: Perly Cracker
2. Hamadryas perlicus
3. Hamadryas perlicus
Common name: Perly Cracker
Anonymous Roles
Not every role needs a name. If you want a role that you don’t expect to use again you can add it directly with but
. You can apply that directly to a class name. This actually creates a new class with the role applied to it. The new class inherits from the original:
并非每个角色都需要一个名字。如果你想要一个不希望再次使用的角色,可以用 but
直接添加它。你可以将其直接应用于类名。这实际上创建了一个应用了角色的新类。新类继承自原始:
class Butterfly {};
my $class-role = Butterfly but role { has $.common-name };
put $class-role.^name; # Butterfly+{<anon|140470326869504>}
say $class-role.^mro; # ((...) (Butterfly) (Any) (Mu))
my $butterfly = $class-role.new:
:common-name( 'Perly Cracker' );
put $butterfly.common-name;
You can do the same thing with less work by removing the variables that stored the classes:
通过删除存中储类的变量,你可以用更少的工作做同样的事情:
my $butterfly2 = ( Butterfly but role { has $.common-name } ).new:
:common-name('Perlicus Cracker');
put $butterfly2.^name;
put $butterfly2.common-name;
That’s still messy. You can apply it to the object directly:
那仍然很混乱。你可以直接将角色应用于对象:
my $butterfly = Butterfly.new;
my $butterfly2 = $butterfly
but role { has $.common-name is rw };
$butterfly2.common-name = 'Perlicus Cracker';
put $butterfly2.^name;
put $butterfly2.common-name;
You can even skip the variable to store the first object. Without the variable to store the initial object you get something a little shorter:
你甚至可以跳过变量来存储第一个对象。如果没有用于存储初始对象的变量,你可以获得更短的内容:
my $butterfly = Butterfly.new
but role { has $.common-name is rw };
$butterfly.common-name = 'Perlicus Cracker';
put $butterfly.^name;
put $butterfly.common-name;
This has the drawback that the original object doesn’t know about the roles, so you can’t set the common name in the constructor. Your role has to allow the object to change the value to set a value.
这样做的缺点是原始对象不知道角色,因此你无法在构造函数中设置公共名称。你的角色必须允许对象更改值以设置值。
Adding a role to an object is handy when you have an object that you may not have created; perhaps it was an argument to your method or the return value from a method you don’t control. In this example you take an argument (and make it is copy
so you can add the role). You call show-common-name
once with a plain Butterfly
. The subroutine sees that the object doesn’t know about common-name
, so it adds it. In your second call to show-common-name
your argument already has the common-name
attribute so it doesn’t need show-common-name
to add it:
当你有一个你可能没有创建的对象时,为一个对象添加一个角色很方便;也许这是你的方法的参数或你不能控制的方法的返回值。在这个例子中,你接受一个参数(并使它可拷贝(is copy
),这样你就可以添加角色)。你用普通的 Butterfly
调用一次 show-common-name
。子例程看到对象不知道 common-name
,所以它添加了它。在第二次调用 show-common-name
时,你的参数已经有了 common-name
属性,所以它不需要 show-common-name
来添加它:
sub show-common-name ( $butterfly is copy ) {
unless $butterfly.can: 'common-name' {
put "Adding role!";
$butterfly = $butterfly
but role { has $.common-name is rw };
$butterfly.common-name = 'Perlicus Cracker';
}
put $butterfly.common-name;
}
# an object without the role
my $butterfly = Butterfly.new;
show-common-name( Butterfly.new );
# an object that already has the role
my $class-role = Butterfly but role { has $.common-name };
show-common-name( $class-role.new: :common-name( 'Camelia' ) );
The output shows that you added the role in your first call but not the second:
Adding role!
Perlicus Cracker
Camelia
When should you apply your role? Whenever it makes sense for your problem.
你应该在什么时候应用你的角色?每当它对你的问题有意义时。
EXERCISE 13.5Take your Lepidoptera
role to its logical conclusion. Start with a new Animalia
role to represent only the kingdom. Create an Arthropoda
role to include the Animalia
role and represent the phylum. Do this all the way down to the Hamadryas genus. From there, create a Hamadrayas
class that inherits from Butterfly
but does all the taxonomic roles down to the genus. From the Hamadrayas
class you should be able to set a species. Make this program work:use lib <.>; use Hamadryas; my $cracker = Hamadryas.new: :species( 'perlicus' ), :common-name( 'Perly Cracker' ), ; put $cracker.binomial-name; put $cracker.common-name;
练习13.5 将你的 Lepidoptera
角色归结为合乎逻辑的结论。从新的 Animalia
角色开始,仅代表王国。创建 Arthropoda
角色以包括 Animalia
角色并代表门。一直这样做到 Hamadryas 属。从那里,创建一个继承自Butterfly
的 Hamadrayas
类,但将所有的分类角色归结为属。从 Hamadrayas
类,你应该能够设置一个物种。使这个程序工作:
use lib <.>;
use Hamadryas;
my $cracker = Hamadryas.new:
:species( 'perlicus' ),
:common-name( 'Perly Cracker' ),
;
put $cracker.binomial-name; put $cracker.common-name;
Summary
You can define common code in a role and reuse it with disparate things. Since it doesn’t create an inheritance relationship it’s perfectly suited for features that don’t define the basic idea of the type.
你可以在角色中定义公共代码,并将其重用于不同的东西。由于它不创建继承关系,因此非常适合未定义类型基本概念的功能。