第十二章. 类

Classes

声明

本章翻译仅用于 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:

而已!看起来这个类是空的,但事实并非如此。即使你没有明确地看到它,你也可以免费获得许多基本行为。尝试调用一些方法。你可以看到它派生自 AnyMu,你可以创建新对象:

% 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创建一个具有 ButterflyMothLobster 的空类定义的单文件程序。为每个类创建一个新对象,即使对象没有做任何有趣的事情。

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将 ButterflyMothLobster 类定义在以它们包含的类命名的单独文件中。类文件应与加载它们的程序位于同一目录中。在程序中加载这些文件并为每个文件创建新对象。

定义方法

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 方法中的默认值可以解决此问题。如果未定义属性,则可以返回空字符串(或 failwarn):

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 Blocks:

读写方法是处理私有属性的一种方法,但你也可以为每种情况创建 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 方法返回 TrueFalse。你可以使用类型对象作为参数(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。这是 submethod 的混合;它就像一个方法,但是一个子类不会继承它(就像你不继承子程序一样):

# $?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 Blocks 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 获得默认值。 .TWEAKInsect 内部运行并在 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. PublicClasscan 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.

类可能是你在程序中组织信息的主要方式,尽管你在本书中没有看到这么多,因为你需要查看主要的语法主题而不是应用程序设计建议。我没有足够的空间来覆盖面向对象的设计或分析,但你绝对应该自己研究它们。正确的设计将使你的生活变得更加轻松。

comments powered by Disqus