声明
本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。
第十二章. 类
A class is the blueprint for an object and manages an object and its behavior. It declares attributes to define what an object will store and methods to define how an object can behave. Classes model the world in a way that makes it easier for your program to do its job.
类是对象的蓝图,用于管理对象及其行为。它声明属性以定义对象将存储的内容以及定义对象行为方式的方法。类以一种使程序更容易完成其工作的方式对世界建模。
I’m mostly going to ignore object-oriented analysis and design. This chapter is about the mechanism of classes and objects. The examples show you how things work and do not endorse a particular way. Use what works for your task and stop using that when it doesn’t.
我大多会忽略面向对象的分析和设计。本章是关于类和对象的机制。这些例子向你展示了如何运作并且不支持某种特定方式。使用适用于你的任务的内容,并在不执行任务时停止使用。
Your First Class
Declare a class by giving it a name and a Block
of code:
通过给出一个名称和一个代码块来声明一个类:
class Butterfly {}
That’s it! It looks like this class is empty, but it’s not. You get much of its basic behavior for free even though you don’t see it explicitly. Try calling some methods on it. You can see that it derives from Any
and Mu
and that you can create new objects:
而已!看起来这个类是空的,但事实并非如此。即使你没有明确地看到它,你也可以免费获得许多基本行为。尝试调用一些方法。你可以看到它派生自 Any
和 Mu
,你可以创建新对象:
% raku
> class Butterfly {}
(Butterfly)
> Butterfly.^mro
((Butterfly) (Any) (Mu))
> my $object = Butterfly.new
Butterfly.new
> $object.^name
Butterfly
> $object.defined
True
You can have as many of these class declarations as you like in one file:
你可以在一个文件中包含任意数量的类声明:
class Butterfly {}
class Moth {}
class Lobster {}
These types are available to your program as soon as they are defined in the code, but not before. If you try to use one before you define it you get a compilation error:
只要在代码中定义了这些类型,就可以使用这些类型,但没定义之前不能使用。如果在定义类之前就尝试使用它,则会出现编译错误:
my $butterfly = Butterfly.new; # Too soon!
class Butterfly {}; # Error: Illegally post-declared type
Instead of defining all of your classes at the beginning of the file (and having to scroll past all of them to get to the good stuff), you’re more likely to want one class per file so you can easily find the class definition again. In that case you can use unit
to declare that the entire file is your class definition. You don’t use a Block
:
而不是在文件的开头定义所有类(并且必须滚动浏览所有类以获得好的东西),你更可能每个文件都需要一个类,这样你就可以轻松地再次找到类定义。在这个例子中,你可以使用 unit
声明整个文件是你的类定义。你没使用Block
:
unit class Butterfly;
Put your class in Butterfly.pm6 (or Butterfly.pm) and load it from your program:
将你的类放在 Butterfly.pm6(或 Butterfly.pm)中并从你的程序中加载它:
use Butterfly;
EXERCISE 12.1Create a single-file program that has the Butterfly
, Moth
, and Lobster
empty class definitions. Create a new object for each, even though the objects don’t do anything interesting yet.
练习12.1创建一个具有 Butterfly
,Moth
和 Lobster
的空类定义的单文件程序。为每个类创建一个新对象,即使对象没有做任何有趣的事情。
EXERCISE 12.2Define the Butterfly
, Moth
, and Lobster
classes in separate files named after the classes they contain. The class files should be in the same directory as the program that loads them. Load those files in your program and create new objects for each.
练习12.2将 Butterfly
,Moth
和 Lobster
类定义在以它们包含的类命名的单独文件中。类文件应与加载它们的程序位于同一目录中。在程序中加载这些文件并为每个文件创建新对象。
定义方法
Methods are like subroutines but know who called them and can be inherited; instead of sub
you define these with method
. This example uses it to output the type name:
方法就像子程序,但知道是谁调用它们并且可以继承; 你用 method
而不是 sub
来定义方法。此示例使用方法来输出类型名称:
class Butterfly {
method who-am-i () { put "I am a " ~ self.^name }
}
Butterfly.who-am-i;
That self
term is the invocant of the method. That’s the object that called the method. It doesn’t need to be in the signature. It also doesn’t need to be in the method. Calling a method on $
does the same thing (you’ll see why later):
那个 self
是该方法的调用者。这是调用方法的对象。它不需要在签名中。它也不需要在方法中。在 $
上调用方法会做同样的事情(稍后你会明白为什么):
class Butterfly {
method who-am-i () { put "I am a " ~ $.^name }
}
Butterfly.who-am-i; # I am a Butterfly
Give the invocant a different name by putting it before a colon in the signature. C++ people might like $this
:
通过将调用者放在签名中的冒号前面,为调用者指定一个不同的名称。 C++ 人可能会喜欢 $this
:
method who-am-i ( $this : ) { put "I am a " ~ $this.^name }
A backslash makes the invocant name a term so you don’t need a sigil:
反斜杠使调用名称成为一个项,因此你不需要使用 sigil:
method who-am-i ( \this : ) { put "I am a " ~ this.^name; }
The default topic can be the invocant, which means that it’s implicit inside the Block
:
默认主题可以是调用者,这意味着它隐含在 Block
中:
method who-am-i ( $_ : ) { put "I am a " ~ .^name; }
If you want to change the invocant name, choose something that describes what it represents:
如果要更改调用者的名称,请选择描述其代表内容的东西:
method who-am-i ( $butterfly : ) { ... }
私有方法
A private method is available only inside the class where it’s defined. You use these to compartmentalize code that you don’t want code outside the class to know about.
私有方法仅在定义它的类中可用。你可以使用它们来划分你不希望类外部代码知道的代码。
Previously who-am-i
directly called .^name
. That’s a very specific way to figure out the “type.” You might want to change that later or use other methods to figure it out, and other methods in your class may need the same thing. Hide it in a method, what's-the-name
:
以前我是谁直接调用 .^name
。这是一种非常具体的方法来确定“类型”。你可能希望稍后更改它或使用其他方法来解决它,并且你的类中的其他方法可能需要相同的东西。将其隐藏在一个方法中,what's-the-name
:
class Butterfly {
method who-am-i () { put "I am a " ~ self.what's-the-name }
method what's-the-name () { self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
put Butterfly.what's-the-name; # Butterfly
That works, but it’s now available as a method that you didn’t intend anyone to use outside of the class. Prefix the method name with a !
to hide it from code outside the class. Replace the method call dot with a !
too:
这起作用了,但它现在可以作为一种方法,你不打算任何人在类外使用。在方法的名字上加上前缀 !
以从类外的代码中隐藏它。也用 !
替换方法调用点:
class Butterfly {
method who-am-i () { put "I am a " ~ self!what's-the-name }
method !what's-the-name () { self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
put Butterfly.what's-the-name; # Butterfly
Now you get an error if you try to use it outside the class:
现在,如果你尝试在类外使用它,则会出现错误:
No such method 'what's-the-name' for invocant of type 'Butterfly'.
Defining Subroutines
A class can contain subroutines. Since subroutines are lexically scoped they are also invisible outside the class. A subroutine can do the same job as a private method. To make this work you need to pass the object as a subroutine argument:
类可以包含子例程。由于子程序是词法作用域的,因此它们在类外也是不可见的。子例程可以执行与私有方法相同的工作。要完成这项工作,你需要将该对象作为子例程参数传递:
class Butterfly {
method who-am-i () { put "I am a " ~ what's-the-name( self ) }
sub what's-the-name ($self) { $self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
对象
Objects are particular instances of a class; sometimes those terms are used interchangeably. Each object has its own variables and data, separate from all the others. Each object, however, still shares the behavior of the class.
对象是类的特定实例;有时这些术语可以互换使用。每个对象都有自己的变量和数据,与其他对象分开。但是,每个对象仍然共享该类的行为。
Start with the simplest class, as before. To create an object you need a constructor method. Any method that creates an object is a constructor. By default that is .new
:
像以前一样,从最简单的类开始。要创建对象,你需要一个构造函数方法。创建对象的任何方法都是构造函数。默认情况下是 .new
:
class Butterfly {}
my $butterfly = Butterfly.new;
The object is a defined instance of the class (the type object is the undefined one). The .DEFINITE
method tells you which one you have:
该对象是类的已定义实例(类型对象是未定义的对象)。 .DEFINITE
方法告诉你用的是哪一个:
put $butterfly.DEFINITE
?? 'I have an object' !! 'I have a type';
提示
Every object also has a .defined
method, but each class can change what that means. Any object of the Failure
class is undefined, so it’s always False
as a conditional. Use .DEFINITE
to avoid that gotcha.
每个对象也有一个 .defined
方法,但每个类都可以改变它的含义。 Failure
类的任何对象都是未定义的,因此它作为条件总是 False
。使用 .DEFINITE
来避免这种问题。
私有属性
Attributes are per-object data. You declare these with has
. The attribute variables use a twigil to denote their access. Before you see the easy way you should see the hard way so you appreciate it more. The $!
twigil defines a private attribute:
属性是每个对象都具有的数据。你用 has
声明属性。属性变量使用 twigil 来表示它们的访问权限。在你看到简单的方法之前,你应该看到困难的方式让你更加欣赏它。 $!
twigil 定义一个私有属性:
class Butterfly {
has $!common-name;
}
By itself this has
definition doesn’t effectively add anything to your class. Nothing can see the attribute, so you have no way to change its value.
这个 has
定义本身并没有有效地为你的类添加任何东西。什么都看不到属性,所以你无法改变它的值。
The special .BUILD
method is automatically called after .new
with the same arguments. You can define your own .BUILD
to bind or assign a value to your private attribute (or do any other work that you want):
使用相同的参数在 .new
之后自动调用特殊的 .BUILD
方法。你可以定义自己的 .BUILD
来绑定值到你的私有属性上或为你的私有属性赋值(或者执行你想要的任何其他工作):
class Butterfly {
has $!common-name;
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
Be careful here. This .BUILD
accepts all named parameters without warning. It doesn’t know which ones you intend to use or what they mean to your class. It’s a default way that almost everything uses to set up objects—but if you misspell a name, you won’t get a warning:
这里要小心。此 .BUILD
接受所有命名参数而不发出警告。它不知道你打算使用哪些或它们对你的类意味着什么。这是几乎所有东西都用来设置对象的默认方式 - 但是如果你拼错了名字,你不会收到警告:
my $butterfly = Butterfly.new: :commen-name('Perly Cracker');
You also don’t get a warning for leaving something out. Maybe you don’t want to require every setting any time you build an object. You might not want this to fail:
你也不会因为遗漏某些属性而得到警告。也许你不希望每次构建对象时就设置好所有的东西。你可能不希望这失败:
my $butterfly = Butterfly.new;
But if you want to require a named parameter you know how to do that. Put a !
after it:
但是如果你想要一个命名参数,你知道如何做到这一点。在命名参数后面放一个 !
:
class Butterfly {
has $!common-name;
method BUILD ( :$common-name! ) { # required now
$!common-name = $common-name;
}
}
For the rest of this example that’s not what you want. You’re going to set default values and provide other ways to change the name.
对于本示例的其余部分,这不是你想要的。你将设置默认值并提供更改名称的其他方法。
You can add an accessor method to allow you to see the name that you’ve stored in the private attribute:
你可以添加一个访问器方法,以允许你查看已存储在私有属性中的名称:
class Butterfly {
has $!common-name;
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
method common-name { $!common-name }
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
This is a problem if you don’t supply a :common-name
. There’s nothing in $!common-name
and you didn’t give .BUILD
anything to work with. When you try to output it you get a warning about the empty value:
如果你不提供 :common-name
,则会出现问题。 $!common-name
中没有任何东西,你没有给 .BUILD
任何东西。当你尝试输出它时,你会收到有关空值的警告:
my $butterfly = Butterfly.new;
put $butterfly.common-name; # Warning!
A default value in the common-name
method could solve this. If the attribute is not defined you could return an empty Str
(or fail
or warn
):
common-name
方法中的默认值可以解决此问题。如果未定义属性,则可以返回空字符串
(或 fail
或 warn
):
method common-name { $!common-name // '' }
属性可以具有默认值:
class Butterfly {
has $!common-name = '';
...
}
你可以使属性的默认值更有趣而不是使用空的字符串
:
class Butterfly {
has $!common-name = 'Unnamed Butterfly';
...
}
my $butterfly = Butterfly.new;
put $butterfly.common-name; # Unnamed Butterfly!
要更改 $!common-name
的值,你可以使用 rw
trait 标记 .common-name
以使其可读可写。如果你为方法赋值,则更改块
中最后一个东西的值(如果你可以修改它,那就是)。这个块
中的最后一个东西是一个 $!common-name
容器:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
method common-name is rw { $!common-name }
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put $butterfly.common-name; # Perly Cracker!
The attributes can be typed like other variables. Constraining the type to Str
means you can assign only thattype to .common-name
:
可以像其他变量一样类型化。将类型约束为Str
意味着你只能将字符串类型赋值给 .common-name
:
class Butterfly {
has Str $!common-name = 'Unnamed butterfly';
...
}
练习12.3 实现一个带有 $!common-name
私有属性的 Butterfly
类。添加 $!color
私有属性。创建一个新的 Butterfly
对象,设置其名称和颜色,然后输出这些值。
Public Attributes
But enough of the hard way. Public attributes do a lot of that work for you. Use $.common-name
with a dot instead of a bang (!
). The accessor method is automatically defined for you and the default .BUILD
handles the setup by filling in the attributes from the named parameters in your call to .new
:
但足够艰难的方式。公共属性为你做了很多工作。使用带点的 $.common-name
而不是 !
。.
将自动为你定义存取方法,默认的 .BUILD
通过在你对 .new
的调用中填充命名参数的属性来处理设置:
class Butterfly {
has $.common-name = 'Unnamed Butterfly'
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
Make it read-write with the rw
trait immediately after the attribute name but before the default value. After you create the object you can assign to the .common-name
method:
在属性名称之后但在默认值之前立即使用 rw
trait 进行读写。创建对象后,可以给 .common-name
方法赋值:
class Butterfly {
has $.common-name is rw = 'An unknown butterfly';
}
my $butterfly = Butterfly.new;
put $butterfly.common-name; # An unknown butterfly
$butterfly.common-name = 'Hamadryas perlicus';
put $butterfly.common-name; # Hamadryas perlicus
The attributes can have types just like other variables. Try to assign the wrong type and you get an exception:
属性可以像其他变量一样具有类型。如果尝试分配错误的类型,你会得到一个异常:
class Butterfly {
has Str $.common-name is rw = 'Unnamed butterfly';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 137; # Error!
To have a mixture of private and public attributes you have to do some work. You probably don’t want to define your own .BUILD
since you’d have to handle everything that the default one does for you. Instead, you can define a private attribute and assign to it later through a method. An rw
trait on the method either returns or assigns to the value of the last thingy in the Block
:
要拥有私有属性和公共属性的混合,你必须费点劲。你可能不想定义自己的 .BUILD
,因为你必须处理默认的一切。相反,你可以定义私有属性,稍后通过方法为其赋值。方法上的 rw
特质要么返回块
中的最后那个东西,要么给块
中最后一个东西赋值:
class Butterfly {
has Str $.common-name is rw = 'Unnamed butterfly';
has Str $!color;
method color is rw { $!color }
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
$butterfly.color = 'Vermillion';
put "{.common-name} is {.color}" with $butterfly;
multi Methods
Read-write methods are one way to handle private attributes, but you can also create multi
methods for each case. Although this example looks simple, your validation and conversion requirements can be arbitrarily complex inside the Block
s:
读写方法是处理私有属性的一种方法,但你也可以为每种情况创建 multi
方法。虽然这个例子看起来很简单,但是你的验证和转换要求在块
中可以是任意复杂的:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
has $!color = 'White';
multi method common-name () { $!common-name }
multi method common-name ( Str $s ) { $!common-name = $s }
multi method color () { $!color }
multi method color ( Str $s ) { $!color = $s }
}
my $butterfly = Butterfly.new;
$butterfly.common-name: 'Perly Cracker';
$butterfly.color: 'Vermillion';
put $butterfly.common-name; # Perly Cracker!
This gets annoying when you have many attributes. There’s another way that you could do this. Return the object in every method that sets a value. This allows you to chain methods to set many attributes in one statement where you don’t repeat the object each time:
当你有许多属性时,这会很烦人。还有另一种方法可以做到这一点。在每个设置值的方法中返回对象。这允许你链接方法以在一个语句中设置许多属性,每次不重复对象:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
has $!color = 'White';
multi method common-name () { $!common-name; }
multi method common-name ( Str $s ) {
$!common-name = $s; self
}
multi method color () { $!color; }
multi method color ( Str $s ) { $!color = $s; self }
}
my $butterfly = Butterfly
.new
.common-name( 'Perly Cracker' )
.color( 'Vermillion' );
put "{.common-name} is {.color}" with $butterfly;
That looks similar to using do given
to topicalize the object and call methods on it:
这类似于使用 do given
来主题化对象并在其上调用方法:
my $butterfly = do given Butterfly.new {
.common-name( 'Perly Cracker' );
.color( 'Vermillion' );
};
put "{.common-name} is {.color}" with $butterfly;
Which technique you use depends on your task and personal preferences. You haven’t seen this with error handling or complex code, either. Those impact your choice too.
你使用哪种技术取决于你的任务和个人喜好。你还没有看到错误处理或复杂代码。那些也会影响你的选择。
Inheriting Types
An existing type might already do most of what you want. Instead of redefining everything that class already does, you can extend it, also known as inheriting from it. Declare the class with is
and the type you want to extend:
现有类型可能已经完成了你想要的大部分工作。你可以扩展它,而不是重新定义类已经执行的所有操作,也称为继承它。使用 is
和要扩展的类型声明类:
class Butterfly is Insect {};
You can do this inside the class definition with also
:
你也可以在类定义中使用 also
执行此操作:
class Butterfly {
also is Insect
};
Here, Insect
is a parent class (or super class or base class). Butterfly
is the child class (or derived type). The terminology isn’t particularly important; the base type is the more general one and the derived type is the more specific one.
在这里,Insect
是一个父类(或超类或基类)。 Butterfly
是子类(或派生类型)。术语不是特别重要;基类型是更通用的类型,派生类型是更具体的类型。
Everything you’ve seen in the Butterfly
class so far (a name and a color) applies to any insect. The name and color attributes are general things that describe any insect, so should be in the more general class. TheButterfly
class now has nothing in it (a “null subclass”), but it should still work the same as it did before:
到目前为止,你在 Butterfly
上看到的所有东西(名称和颜色)都适用于任何昆虫。名称和颜色属性是描述任何昆虫的通用的东西,因此应该在更通用的类中。 Butterfly
类现在没有任何内容(“null子类”),但它应该仍然像以前一样工作:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $.color is rw = 'Brown';
}
class Butterfly is Insect {}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
$butterfly.color = 'Vermillion';
put "{.common-name} is {.color}" with $butterfly;
Butterfly
can have its own $.color
that overrides the one from Insect
. Declaring the attribute inButterfly
effectively hides the one in its parent class:
Butterfly
可以有自己的 $.color
来覆盖 Insect
的那个属性。在 Butterfly
中声明属性有效地隐藏了其父类中的属性:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $.color is rw = 'Brown';
}
class Butterfly is Insect {
has $.color is rw = 'Mauve';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
# Perly Cracker is Mauve
put "{.common-name} is {.color}" with $butterfly;
Sometimes that’s not the right thing to do. The parent class might need to run some code in its version of the method to make everything else work. Instead of hiding the parent method you want to wrap it (or extend it).
有时这不是正确的事情。父类可能需要在其方法版本中运行一些代码才能使其他所有东西都有效。你想要包装它(或扩展它)的父方法而不是隐藏父方法。
The callsame
routine can do this for you. It redispatches the call with the same arguments. You run the parent method in your child method:
callsame
程序可以为你执行此操作。它使用相同的参数重新调度调用。你在子方法中运行父方法:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $!color = 'Brown';
method color is rw {
put "In Insect.color!";
$!color
}
}
class Butterfly is Insect {
has $!color = 'Mauve';
method color is rw {
put "In Butterfly.color!";
my $insect-color = callsame;
put "Insect color was {$insect-color}!";
$!color
}
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
Inheritance isn’t the only way to add features to your class. You should save inheritance for specific cases where your class is a more specific type of the same thingy.
继承不是向类添加功能的唯一方法。对于特定情况,你应该保存继承,其中你的类是更具体的类型。
EXERCISE 12.4Create classes for the kingdom, phylum, class, order, family, and genus of a Hamadryas butterfly. The phylum inherits from kindgom, the class inherits from phylum, and so on. Each class notes its place in the hierarchy:class Nymphalidae is Lepidoptera { }
Define a .full-name
method in Hamadryas
to join all the levels together.The genus Hamadryas is classified in Animalia, Arthropodia, Insecta, Lepidoptera, and Nymphalidae.
练习12.4 为Hamadryas 蝴蝶的王国,门,阶级,秩序,家庭和属创建类。门继承自 kindgom,该类继承自门,等等。每个班级都记录了它在层次结构中的位置: class Nymphalidae is Lepidoptera { }
在 Hamadryas
中定义一个完整的名称方法以将所有级别连接在一起.Hamadryas属被分类为Animalia,Arthropodia,Insecta,Lepidoptera和Nymphalidae。
Checking Inheritance
You’ve already seen .^mro
to get a List
of classes. The .isa
method returns True
or False
if the type you specify is in that List
. You can test a type or an object with a type object as the argument (a Str
):
你已经看过 .^mro
获得一个类列表
。如果你指定的类型在该列表
中,则 .isa
方法返回 True
或 False
。你可以使用类型对象作为参数(Str
)来测试类型或对象:
put Int.isa: 'Cool'; # True
put Int.isa: Cool; # True
put Butterfly.isa: Insect; # True;
put Butterfly.isa: Int # False;
my $butterfly-object = Butterfly.new;
put $butterfly.isa: Insect; # True
Smart matching does the same job. That’s what when
is checking if you give it only a type:
智能匹配可以完成同样的工作。这就是 when
检查你是否只给它一个类型:
if Butterfly ~~ Insect {
put "Butterfly is an Insect";
}
if $butterfly ~~ Insect {
put "Butterfly is an Insect";
}
put do given $butterfly {
when Int { "It's a integer" }
when Insect { "It's an insect" }
}
You may have been wondering about the name of the .^mro
method. That’s for method resolution order in cases where you inherit from multiple classes:
你可能一直想知道 .^mro
方法的名称。这是在你从多个类继承的情况下的方法解析顺序:
class Butterfly is Insect is Flier {...}
I’m not going to tell you more about multiple inheritance in the hopes that you never do it. It’s possible, but you’ll likely solve your problem with the roles you’ll see in Chapter 13.
我不会告诉你更多关于多重继承的信息,希望你永远不会这样做。这是可能的,但你可能会用你在第13章中看到的角色来解决你的问题。
Stub Methods
A parent class can define a method but not implement it—this is known as an abstract method (or stub method). Use !!!
inside the Block
to denote that something later will implement a method with that name:
父类可以定义一个方法但不实现它 - 这称为抽象方法(或存根方法)。在块
中使用 !!!
表示稍后会实现具有该名称的方法:
class Insect {
has $.color is rw = 'Brown';
method common-name { !!! }
}
class Butterfly is Insect {
has $.color is rw = 'Mauve';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
When you run this the !!!
throws an exception:
当你运行这个代码时 !!!
抛出异常:
Stub code executed
Instead of the !!!
you can use ...
. The triple dot calls fail
instead of die
. Either way something else needs to implement that method. A public attribute would do that for you:
你可以使用 ...
而不是 !!!
。三点调用 fail
而不是 die
。无论哪种方式,其他东西都需要实现该方法。公共属性会为你执行此操作:
class Butterfly is Insect {
has $.common-name is rw;
has $.color is rw = 'Mauve';
}
Controlling Object Creation
Sometimes you want more control over your object creation. When you call .new
there are several steps and you’re able to hook into each of them. You don’t need all the gory details at the programmer level so I’ll spare you.
有时你希望更好地控制对象创建。当你调用 .new
时,有几个步骤,你可以接入每个步骤。你不需要程序员级别的所有残酷细节,所以我会饶了你。
When you call .new
you’re reaching into the root of the object system, Mu
. .new
calls .bless
, which actually creates your object. Now you have an empty object. It’s not quite ready for use yet.
当你调用 .new
时,你正在进入对象系统的根,即 Mu
。 .new
调用 .bless
,它实际上创建了你的对象。现在你有一个空对象。它尚未准备好使用。
.bless
does some more work by calling .BUILDALL
on your empty object, passing it all the same arguments that you passed to .new
. .BUILDALL
visits each class in your inheritance chain, starting with Mu
. You typically don’t want to mess with .BUILDALL
since it’s driving the process rather than affecting your objects.
.bless
通过在空对象上调用 .BUILDALL
来做更多工作,并将所有传递给 .new
的相同参数传递给它。 .BUILDALL
访问继承链中的每个类,从 Mu
开始。你通常不希望混淆 .BUILDALL
,因为它正在推动流程而不是影响你的对象。
.BUILDALL
calls the .BUILD
method in your class if you’ve defined one. .BUILD
gets the same arguments as .new
. This is how your attributes get their values from your arguments. If no class defined a .BUILD
you get the default one that fills in your attributes from the named parameters.
如果你定义了一个,则 .BUILDALL
会在你的类中调用 .BUILD
方法。 .BUILD
获得与 .new
相同的参数。这是你的属性从参数中获取其值的方式。如果没有类定义 .BUILD
,你将获得从命名参数填充属性的默认类。
NOTE
The default object creation mechanism wants to work with named parameters. You could rework everything for positional parameters but that would be a lot of work.
默认对象创建机制希望使用命名参数。你可以为位置参数重做一切,但这将是很多工作。
After .BUILD
is done you have a completely built object that’s ready for use (but not the final object yet). The .TWEAK
method gives you a chance to adjust that object before you move on to the next class to go through the process again.
在 .BUILD
完成后,你有一个完全构建的对象可以使用(但还没有最终的对象)。 .TWEAK
方法让你有机会调整该对象,然后再转到下一个类再次完成该过程。
You should declare both .BUILD
and .TWEAK
with submethod
. This is a hybrid of sub
and method
; it acts just like a method but a subclass doesn’t inherit it (just like you don’t inherit subroutines):
你应该用 submethod
声明 .BUILD
和 .TWEAK
。这是 sub
和 method
的混合;它就像一个方法,但是一个子类不会继承它(就像你不继承子程序一样):
# $?CLASS is a compile-time variable for the current class
# &?ROUTINE is a compile-time variable for the current routine
class Insect {
submethod BUILD { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
submethod TWEAK { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
}
class Butterfly is Insect {
submethod BUILD { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
submethod TWEAK { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
}
my $b = Butterfly.new;
The .TWEAK
method is called before .BUILDALL
moves on to the next class:
在 .BUILDALL
之前调用 .TWEAK
方法进入下一个类:
In Insect.BUILD
In Insect.TWEAK
In Butterfly.BUILD
In Butterfly.TWEAK
Now that you’ve seen the order in which things happen, let’s look at each step a little more closely.
现在你已经看到了事情发生的顺序,让我们更仔细地看一下每一步。
Building Objects
.BUILD
lets you decide how to treat your newly created object. Start with a submethod
that does nothing:
.BUILD
让你决定如何对待新创建的对象。从一个什么都不做的 submethod
开始:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD {} # does nothing
}
my $butterfly = Butterfly.new: :color('Magenta');
put "The butterfly is the color {$butterfly.color}";
The color isn’t set and you get a warning about an uninitialized value:
颜色未设置,你会收到有关未初始化值的警告:
The butterfly is the color
Use of uninitialized value of type Any in string context.
.BUILDALL
found your .BUILD
so it used your version to set up the object. The color value in your call to .new
isn’t assigned to the $!color
attribute because your empty .BUILD
didn’t handle that. You need to do that yourself. By default all the named parameters are in %_
and .BUILD
gets all of the same arguments as.new
:
.BUILDALL
找到你的 .BUILD
所以它用你的版本来设置对象。调用 .new
的颜色值未分配给 $!color
属性,因为空的 .BUILD
没有处理它。你需要自己做。默认情况下,所有命名参数都在 %_
和 .BUILD
中获取所有相同的参数作为 .new
:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD {
$!color = %_<color>;
}
}
Use the argument list for .BUILD
to automatically define some named parameters to variables:
使用 .BUILD
的参数列表自动为变量定义一些命名参数:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color;
}
}
If you don’t specify a color
named argument you get another warning because the value in $color
is uninitialized. In some cases you might want that named parameter to be required, so you put a !
after it:
如果未指定 color
命名参数,则会收到另一个警告,因为 $color
中的值未初始化。在某些情况下,你可能希望该命名参数是必需的,所以你放一个 !
之后:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD ( :$color! ) {
$!color = $color;
}
}
Other times you might want to set a default value. Another attribute won’t work because the object build process hasn’t set its default value yet:
其他时候你可能想要设置默认值。另一个属性不起作用,因为对象构建过程尚未设置其默认值:
class Butterfly {
has $!default-color = 'Wine'; # Won't work
has $.color;
has $.common-name;
submethod BUILD ( :$color! ) {
$!color = $color // $!default-color; # No setup yet!
}
}
A private method could work; a private method can only be seen from code inside the class and cannot be inherited. A submethod
isn’t inheritable either but is still a public method:
私有方法可以工作;私有方法只能从类中的代码中看到,不能被继承。submethod
也不是可继承的,但仍然是一种公共方法:
class Butterfly {
method default-color { 'Wine' }
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color // self.default-color;
}
}
Class variables can do the same job. A lexical variable defined in the class Block
is only visible to the code in the same Block
and the Block
s inside it:
类变量可以完成相同的工作。类Block
中定义的词法变量仅对同一块中的代码及其中的块可见:
class Butterfly {
my $default-color = 'Wine';
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color // $default-color;
}
}
What’s more interesting to .BUILD
is the extra setup you don’t want to be part of the interface. Perhaps you want to track when you used the default value so you can distinguish it from the case where the specified color happened to be the same:
更有趣的是 .BUILD
是你不希望成为界面一部分的额外设置。你可能希望跟踪何时使用默认值,以便将其与指定颜色恰好相同的情况区分开来:
class Butterfly {
my $default-color = 'Wine';
has $.used-default-color;
has $.color;
submethod BUILD ( :$color ) {
if $color {
$!color = $color;
$!used-default-color = False;
}
else {
$!color = $default-color;
$!used-default-color = True;
}
}
}
my $without = Butterfly.new;
put "Used the default color: {$without.used-default-color}";
my $with = Butterfly.new: :color('Wine');
put "Used the default color: {$with.used-default-color}";
Even though those two butterflies are the same color, you know which one specified a color and which one didn’t:
即使这两只蝴蝶是相同的颜色,你知道哪一种指定了颜色,哪一种没有指定颜色:
Used the default color: True
Used the default color: False
Tweaking Objects
When you create an object you can use .TWEAK
to set either the color you supplied as a named argument or the default color:
创建对象时,可以使用 .TWEAK
设置作为命名参数提供的颜色或默认颜色:
class Insect {
has $!default-color = 'Brown';
has $.common-name is rw = 'Unnamed insect';
has $.color is rw;
submethod TWEAK ( :$color ) {
self.color = $color // $!default-color;
}
}
class Butterfly is Insect {}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
The output shows that you got the default from Insect
. .TWEAK
ran inside Insect
and set an attribute inside Insect
. The .color
method is defined in Insect
so it works out:
输出显示你从 Insect
获得默认值。 .TWEAK
在 Insect
内部运行并在 Insect
中设置了一个属性。 .color
方法在 Insect
中定义,因此它可以解决:
Perly Cracker is Brown
If you specify a color, that color is actually set:
如果指定颜色,则实际设置该颜色:
my $butterfly = Butterfly.new: :color('Purple');
You can modify Butterfly
to have its own default color and .TWEAK
. The .TWEAK
method is the same but you wouldn’t want to inherit it. It depends on the presence of an attribute that it can’t know the child class has:
你可以修改 Butterfly
以拥有自己的默认颜色和 .TWEAK
。 .TWEAK
方法是相同的,但你不希望继承它。它取决于它无法知道子类具有的属性的存在:
class Butterfly is Insect {
has $.default-color = 'Vermillion';
submethod TWEAK ( :$color ) {
self.color = $color // $!default-color;
}
}
Private Classes
You can declare classes with my
to make them private to the current scope. At the file level that class is only available in that file. If you load the file that contains it you won’t be able to see it:
你可以使用 my
声明类,使其成为当前范围的私有。在文件级别,该类仅在该文件中可用。如果你加载包含它的文件,你将无法看到它:
# PublicClass.pm6
my class PrivateClass { # Hidden from outside the file
method hello { put "Hello from {self.^name}" }
}
class PublicClass {
method hello { PrivateClass.hello }
}
In your program you can load PublicClass
and call a method on the PublicClass
type. PublicClass
can see PrivateClass
because it’s in the same file. From your program you can’t call PrivateClass
directly, though. That scope doesn’t know about that type:
在你的程序中,你可以加载 PublicClass
并在 PublicClass
类型上调用方法。 PublicClass
可以看到PrivateClass
,因为它在同一个文件中。但是,从你的程序中,你无法直接调用 PrivateClass
。该范围不知道该类型:
use PublicClass;
PublicClass.hello; # Hello from PrivateClass
PrivateClass.hello; # Error: Undeclared name: PrivateClass
If you need a class only inside another class (and not the rest of the file), you can declare it inside the class. This can be handy to compartmentalize and organize behavior inside a class:
如果你只需要一个类在另一个类(而不是文件的其余部分)中,你可以在类中声明它。这可以方便地划分和组织类中的行为:
class Butterfly {
my class PrivateHelper {}
}
Private classes are a great tool when you want to compartmentalize some behavior that you need but don’t want to expose to normal users. You can use them for intermediate objects that the main program never need know exist.
当你想要划分某些你需要但不想向普通用户公开的行为时,私有类是一个很好的工具。你可以将它们用于主程序永远不需要知道的中间对象。
EXERCISE 12.5Create a Butterfly
class that contains a private class that tracks when the object was created and updated. Use it to count the number of updates to the class. A method in Butterfly
should access the private class to output a summary.
EXERCISE 12.5创建一个 Butterfly
类,它包含一个私有类,用于跟踪对象何时被创建和更新。用它来计算类的更新次数。 Butterfly
中的方法应该访问私有类以输出摘要。
Summary
Classes will likely be your main way of organizing information in your programs, though you don’t see it so much in this book because you need to see mostly syntactic topics rather than application design advice. I didn’t have the space for good coverage of object-oriented design or analysis, but you should definitely research those on your own. The right design will make your life so much easier.
类可能是你在程序中组织信息的主要方式,尽管你在本书中没有看到这么多,因为你需要查看主要的语法主题而不是应用程序设计建议。我没有足够的空间来覆盖面向对象的设计或分析,但你绝对应该自己研究它们。正确的设计将使你的生活变得更加轻松。