標題
大綱 12: 對象(Objects)
版本
創(chuàng)建于: 2004-08-27
上次修改時間: 2014-8-26
版本:134
概述
這個大綱總結了第12個啟示錄, 它探討關于面向對象的編程订框。
類 (Classes)
S12-class/lexical.t lines 12–61
S14-roles/lexical.t lines 12–47
類是使用關鍵字 class
聲明的模塊矩桂。 至于模塊, 即公共存儲, 接口, 并且類的名字通過包和它的名字來表示, 這總是(但不必須)一個全局的名字凰萨。 類是一個模塊, 因此能導出東西, 但是類添加了更多的行為來支持 Perl 6 的標準的基于類的 OO百匆。
作為類型對象(type object), 類名代表了它的類型的所有可能值, 因此在計算那種類型的普通對象能做什么時, 類型對象能用作任何屬于該類型的"真實"對象的代理潘拨。 類對象是一個對象, 但是它不是一個類(Class
), 因為 Perl 6 中沒有強制性的 Class
類, 還因為在Perl 6 中類型對象被認為是未定義的非迹。 我們想基于類的和基于原型的 OO 編程這兩個都支持惩歉。所以, 所有的元編程是通過當前對象的 HOW
對象來完成的, 這可以把元編程代理給任何它喜歡的元模型上抡谐。 然而, 默認地, 從 Mu
派生的對象支持相當標準的基于類的模型裁奇。
有兩種基本的類聲明語法:
unit class Foo; # 文件的剩余部分是類的定義
has $.foo
class Bar { has $.bar } # block 是類的定義
第一種形式只有當?shù)谝环N聲明是在一個編譯單元(即文件或 EVAL 字符串)中時被允許。
如果類的主體以一個主操作符為單個prefix:<...>
(yada)listop 開始的語句, 那么只引入類名而不定義, 并且在同一個作用域中第二次聲明那個類不會抱怨重新定義麦撵。(語句修飾符允許在這樣的 ...
操作符上刽肠。)因此你可以向前聲明你的類:
calss A { ... } # 引入 A 作為類名而不定義
class B { ... } # 引入 B 作為類名而不定義
my A $root .= new(:a(B));
class A {
has B $.a;
}
class B {
has A $.b;
}
就像這個例子展示的那樣, 這允許互相遞歸類的定義(但是它不允許遞歸繼承)。
通過 augment
聲明符來擴展類也是可以的, 但是那被認為有點不符合常規(guī)并且不應用于向前聲明免胃。
一個具名的類聲明能作為表達式的一部分出現(xiàn), 就像具名子例程聲明那樣音五。
類主要用于實例管理, 而非代碼復用。 當你只是想提取共有的代碼時考慮使用 roles羔沙。
Perl 6 支持多重繼承, 匿名類和自動裝箱躺涝。
S12-class/anonymous.t lines 5–81
所有的 public 方法調用在 C++ 里就是虛的。
你可能派生自任何內置類型, 但是像Int
這樣的低級別派生可能只增加行為, 而不改變表示扼雏。使用構成 and/or 代理來改變表示坚嗜。
因為 Perl 6 中沒有裸字, 裸的類名必須被預先聲明好。你可以預先聲明一個 stub 類并在之后填充它就像你在子例程中的那樣诗充。
S12-class/declaration-order.t lines 14–21
你可以使用 ::
前綴來強制一個名字解釋為類名或類型名苍蔬。 在左值上下文中, ::
前綴是一個 no-op, 但是在聲明式的上下文中, 它在聲明的作用域中綁定了一個新的類型名, 伴隨著任何其它通過聲明聲明的東西。
S12-class/literal.t lines 7–25
如果沒有 my
或其它作用域聲明符, 那么一個裸的 class
聲明符聲明了一個 our
聲明符, 即一個在當前包中的名字蝴蜓。 因為類文件開始解析于 GLOBAL
包中, 文件中第一個聲明的類把它自己安裝為一個全局的名字, 之后的聲明隨后把它們自己安裝在當前類中而不是全局的包中碟绑。
因此, 要在當前的包(或模塊, 或類)中聲明一個內部的類, 那使用 our class
或僅僅 class
。 要聲明一個本地作用域的類, 使用 my class
茎匠。 類的名字總是從最內的作用域開始搜索, 直到最外層的作用域格仲。 至于起始的 ::
, 類名中出現(xiàn)的 ::
不是暗示全局性(不像 Perl 5)。 所以外層的搜索能查看搜索過的名字空間的孩子汽抚。
內部的 class 或 role 在一般的上下文中必須被本地作用域化, 如果它依賴于任何一般的參數(shù)或類型的話; 并且這樣的內部類或 role 也叫做泛型抓狭。
類的特性(Class traits)
類的特性使用 is
來設置:
class MyStruct is rw { ... }
單繼承
isa
也僅僅是一個特性, 碰巧是另一個類:
class Dog is Mammal { ... }
多重繼承
多重繼承使用多個 is
來指定:
class Dog is Mammal is Pet { ... }
?```
#### 合成
Roles 使用 `does` 代替 `is`:
?```perl
class Dog is Mammal does Pet { ... }
also 聲明符
你也可以使用 also
聲明符把這些都放在里面:
class Dog {
also is Mammal;
also does Pet;
}
(然而, also 聲明符主要用在 roles 中)
元類(Metaclasses)
每個對象(包括任何基于類的對象)代理給它的元類的一個實例上伯病。你能通過 HOW
方法來獲取元類的任何對象, HOW 方法返回那個元類的實例造烁。 在 Perl 6 中, 一個"類"對象僅僅被認為是一個"空的"實例, 更合適的叫法是 "原型" 或 "泛型" 對象, 或僅僅叫 "類型對象"否过。 Perl 6 真的沒有任何名為 Class
的類。 各種各樣的類型是通過這些未定義的類型對象來命名的, 這被認為是和他們自己的實例化版本擁有相同的類型惭蟋。但是這樣的類型對象是惰性的, 并且不能管理類實例的狀態(tài)苗桂。
管理實例的實際對象是通過 HOW 語法所指向的元類對象。 所以當你說 "Dog"的時候, 你指的即是一個包又是一個類型對象, 后者指的是通過 HOW 來表示類的對象告组。 類型對象區(qū)別實例對象不是通過擁有不同的類型, 而是就誰被定義而言的煤伟。有些對象可能告訴你它們被定義了, 而其它對象可能告訴你它們沒有被定義。 那歸結于對象, 并取決于元對象如何選擇去分發(fā) .defined
方法木缝。
閉合類(Closed classes)
類默認是開放和非最終(non-final) 的, 但是它們能很容易地被整個程序閉合或定型, 而非被它們自己便锨。 然而使用動態(tài)加載或子程序的平臺可能不想批量閉合或定型類。(這特么都是什么?)
私有類
私有類能使用 my
來聲明; 在 Perl 6 中, 大部分隱私問題是使用詞法作用域(my)來處理的我碟。詞法默認很重要的事實也意味著類導入的任何名字默認也是私有的放案。
在 grammars 中, 不能使用 grammars 屬性, 所以你能從一個不相關的 grammar 中調用一個 grammar。這能通過在閉包中創(chuàng)建一個本地作用域的 grammars 來模仿那種行為矫俺。閉包捕獲的詞法變量就能用在像 grammars 屬性那樣的地方了吱殉。
類的成分
class
聲明(特別地, role 合成)是嚴格的編譯時語句。特別地, 如果類聲明出現(xiàn)在嵌套作用域里面, 那么類聲明被約束為, 構成和任何可能的實現(xiàn)一樣厘托。所有的 roles 和 超類必須被限制為非重新裝訂的只讀值; 任何 traits 的參數(shù)會只在非拷貝上下文中被求值友雳。類聲明限定的名字是非重新裝訂的并且是只讀的, 所以它們能被用作超類。
匿名的類聲明
在匿名的類聲明中, 如果需要 ::
本身就代表了匿名類的名字:
class { ... } # ok
class is Mammal { ... } # 錯誤
class :: is Mammal { ... } # ok
class { also is Mammal; ... } # also ok
方法
方法是類中使用 method
關鍵字聲明的子例程:
method doit ($a, $b, $c) { ... }
meyhod doit ($self: $a, $b, $c) { ... }
method doit (MyName $self: $a, $b, $c) { ... }
method doit (::?CLASS $self: $a, $b, $c) { ... }
調用者
調用者的聲明是可選的铅匹。你總是使用關鍵字 self
來訪問當前調用者押赊。你不需要聲明調用者的類型, 因為調用者的詞法類是被任何事件知曉的, 因為方法必須聲明在調用者的類中, 盡管真實的(虛擬的)類型可能是詞法類型派生出來的類型。你可以聲明一個限制性更強的類類型, 但是那對于多態(tài)可能是壞事兒包斑。你可以顯式地使用詞法類型來type 調用者, 但是任何為此做的檢查會被優(yōu)化掉考杉,(當前的詞法導向的類總是可以命名為 ::?CLASS
即使在匿名類中或 roles 中)
S12-attributes/recursive.t lines 46–97
要標記一個顯式的調用者, 在它后面放上一個冒號就好了:
method doit ($x: $a, $b, $c) { ... }
如果你使用數(shù)組變量為 Array 類型聲明一個顯式的調用者, 你可以在列表上下文中直接使用它來生成它的元素
method push3 (@x: $a, $b, $c) { ... any(@x) ... }
注意 self
項直接指向了方法所調用的對象上, 因此:
class A is Array {
method m() { .say for self }
}
A.new(1, 2, 3).m; # 1\n2\n\3
會打印3行輸出。
私有方法
私有方法是使用 !
聲明的:
[S12-methods/private.t lines 6–44]
method !think (Brain $self: $thought)
(這樣的方法對普通方法調用是完全不可見的, 實際上是使用不同的語法, 即使用 !
代替 .
字符舰始。 看下面崇棠。)
方法作用域
不像大部分的其它聲明符, method
聲明符不是默認為 our
語義, 或者甚至 my
語義, 而是 has
語義。所以, 不是安裝一個符號到詞法的或包的符號表中, 它們只是在當前類或 role 中通過調用它的元對象來安裝一個公共的或私有的方法丸卷。(同樣適用于 submethod
聲明符 — 查看下面的 "Submethod").
使用一個顯式的 has
聲明符對聲明沒有影響枕稀。你可以在本地作用域中使用my
或在當前包中使用 our
來給方法安裝額外的別名。這些別名使用 &foo
別名來命名, 并返回一個能叫做子例程的 Routine
對象, 這時你必須提供期望的調用者作為第一個參數(shù)谜嫉。
方法調用
要使用普通的方法分發(fā)語義來調用普通的方法, 使用點語法記法或間接對象記法:
S12-methods/instance.t lines 13–243
$obj.doit(1,2,3)
doit $obj: 1, 2, 3
間接對象記法現(xiàn)在要求調用者后面要有一個冒號, 即使冒號后面沒有參數(shù):
S12-methods/indirect_notation.t lines 5–57
$handle.close;
close $handle:;
要拒絕方法調用并且只考慮 subs, 僅僅從調用行那兒省略冒號即可:
close($handle);
close $handle;
然而, 這兒內置IO
類定義的方法 close ()
是導出的, 它默認把 multi sub close (IO)
放在作用域中萎坷。因此, 如果 $handle
對象是一個 IO 對象的話, 那么上面的兩個子例程調用仍舊被轉換成方法調用。
點調用記法可以省略調用者, 如果調用者是 $_
:
.doit(1,2,3)
方法調用使用的是 C3 方法解析順序沐兰。
花哨的方法調用
注意對于私有方法沒有對應的記法哆档。
!doit(1,2,3) # 錯, 會被解析為 not(doit(1,2,3))
self!doit(1,2,3) # ok
對于方法名有幾種間接的形式。你可以使用引起的字符串替換標識符, 它會被求值為引起, 引起的結果用作方法名住闯。
S12-methods/indirect_notation.t lines 58–76
$obj."$methodname"(1,2,3) # 使用 $methodname 的內容作為方法名
$obj.'$methodname'(1,2,3) # 沒有插值; 調用名字中帶有 $ 符號的方法
$obj!"$methodname"() # 間接調用私有方法名
在插值中, 雙引號形式不可以包含空白瓜浸。這在雙引號中以點結尾的字符串中達到用戶期望的那樣:
S02-literals/misc-interpolation.t lines 96–120
say "Foo = $foo.";
如果你真的想調用帶有空格的方法, 那你使用一個閉包插值來進行約束:
say "Foo = {$foo."a method"()}"; # OK