Raku 程式設計/類和屬性
到目前為止,我們已經看到了面向過程程式設計的構建塊:表示式、分支、迴圈和子例程的列表,它們告訴計算機該做什麼以及如何做。Raku 很好的支援面向過程程式設計,但這並不是 Raku 支援的唯一程式設計風格。Raku 很好的適應的另一個常見正規化是面向物件方法。
物件是資料及其操作的組合。物件的“資料”稱為其屬性,物件的“操作”稱為其方法。從這個意義上說,屬性定義了物件的“狀態”,而方法定義了物件的“行為”。
類是建立物件的模板。當提到特定類的物件時,通常將該物件稱為該類的例項。
類使用 class 關鍵字定義,並被賦予一個名稱
class MyClass {
}
在類宣告內部,您可以定義屬性、方法或子方法。
屬性使用 has 關鍵字定義,並使用特殊的語法指定。例如,考慮以下類
class Point3D {
has $!x-axis;
has $!y-axis;
has $!z-axis;
}
Point2D 類定義了 3D 座標系中的一個點,它有三個屬性,分別名為 x-軸、y-軸和 z-軸。
在 Raku 中,所有屬性都是私有的,一種明確表達這一點的方式是使用 ! twigil。使用 ! twigil 宣告的屬性只能在類內部透過使用 !attribute-name 直接訪問。用這種方式宣告屬性的另一個重要後果是,物件不能使用預設的 new 建構函式進行填充。
如果您使用 . twigil 而不是 ! twigil 宣告屬性,則會自動生成一個只讀訪問器[檢查拼寫] 方法。您可以將 . twigil 視為“屬性 + 訪問器”。這個訪問器,它是一個以其屬性命名的“方法”,可以從類外部呼叫並返回其屬性的值。為了允許透過提供的訪問器更改屬性,必須將 is rw 特性新增到屬性中。
之前的 Point3D 類可以宣告如下
class Point3D {
has $.x-axis;
has $.y-axis;
has $.z-axis is rw;
}
鑑於 . twigil 聲明瞭一個 ! twigil 和一個訪問器[檢查拼寫] 方法,即使屬性使用 . twigil 宣告,也可以始終使用 ! twigil 使用它們。
方法的定義與普通子例程相同,只是有一些關鍵區別
- 方法使用
method關鍵字而不是sub。 - 方法具有特殊的變數
self,它引用正在呼叫方法的物件。這被稱為呼叫者。 - 方法可以直接訪問物件的內部特性。
在定義方法時,您可以為呼叫者指定不同的名稱,而不是必須使用 self。為此,您將其放在方法簽名的開頭,並用冒號將其與簽名的其餘部分隔開
method myMethod($invocant: $x, $y)
在此上下文中,冒號被視為一種特殊的逗號型別,因此您可以用額外的空格來寫它,如果這樣更容易
method myMethod($invocant : $x, $y)
以下是一個示例
class Point3D {
has $.x-axis;
has $.y-axis;
has $.z-axis;
method set-coord($new-x, $new-y, $new-z) {
$!x-axis = $new-x;
$!y-axis = $new-y;
$!z-axis = $new-z;
}
method print-point {
say "("~$!x-axis~","~$!y-axis~","~$!z-axis~")";
}
# method using the self invocant
method distance-to-center {
return sqrt(self.x-axis ** 2 + self.y-axis ** 2);
}
# method using a custom invocant named $rect
method polar-coordinates($rect:) {
my $r = $rect.distance-to-center;
my $theta = atan2($rect.y-axis, $rect.x-axis);
return "("~$r~","~$theta~","~$rect.z-axis~")";
}
}
物件是其型別為給定類的“資料項”。物件包含類定義的任何屬性,並且還可以訪問類中的任何方法。物件使用 new 關鍵字建立。
使用 Point3D 類
my $point01 = Point3D.new();
類建構函式,new() 可以接受命名方法,這些方法用於初始化類的任何屬性
# Either syntax would work for object initialization
my $point01 = Point3D.new(:x-axis(3), :y-axis(4), :z-axis(6));
my $point02 = Point3D.new(x-axis => 3, y-axis => 4, z-axis => 6);
類中的方法使用點符號呼叫。這是物件、一個句點,然後是該方法的名稱。
$point01.print-point();
say $point01.polar-coordinates();
當沒有為點符號方法呼叫提供物件時,將使用預設變數 $_ 代替
$_ = Point3D.new(:x-axis(6), :y-axis(8), :z-axis(6));;
.print-point();
say .polar-coordinates();
基本類系統使“資料”及其操作該資料的“程式碼例程”能夠以邏輯的方式捆綁在一起。但是,大多數類系統還有更高階的功能,這些功能也允許繼承,這使得類能夠相互構建。繼承是類形成邏輯層次結構的能力。Raku 支援類的正常繼承和子類,但也支援稱為mixin和角色的特殊高階功能。我們將不得不為後面的章節保留其中一些更難的功能,但我們將在本章中介紹一些基礎知識。
我們在前面的章節中討論了 Perl 的一些基本型別。您可能會驚訝地發現,所有 Raku 資料型別都是類,並且所有這些值都具有可以使用的內建方法。在這裡,我們將討論一些可以對到目前為止看到的各種物件呼叫的方法。
我們已經看到了內建函式 print 和 say。所有內建類都有相同名稱的方法,這些方法列印物件的字串形式。
我們將快速討論一下 eval 函式。eval 使我們能夠在執行時編譯並執行 Raku 程式碼字串。
eval("say 'hello world!';");
所有 Raku 物件都具有一個名為 .perl 的方法,該方法返回表示該物件的 Raku 程式碼字串。
my Int $x = 5;
$x.perl.say; # "5"
my @y = (1, 2, 3);
@y.perl.say; # "[1, 2, 3]"
my %z = :first(1), :second(2), :third(3);
%z.perl.say; # "{:first(1), :second(2), :third(3)}"
有一些方法可以顯式地將給定的資料項更改為不同的形式。這就像一種強制給定的資料項以不同的上下文進行解釋的顯式方法。以下是一個部分列表
.item- 返回標量上下文的專案。
.iterator- 返回物件的迭代器。我們將在後面的章節中討論迭代器。
.hash- 返回雜湊上下文的物件
.list- 返回陣列或“列表”上下文的物件
.Bool- 返回物件的布林值
.陣列- 返回包含物件資料的陣列
.雜湊- 返回包含物件資料的雜湊
.迭代器- 返回物件的迭代器。我們將在後面的章節中討論迭代器。
.標量- 返回指向物件的標量引用
.字串- 返回物件的字串表示
.WHENCE- 返回物件型別自動建立閉包的程式碼引用。我們稍後將討論自動建立和閉包。
.WHERE- 返回資料物件的記憶體位置地址
.WHICH- 返回物件的標識值,對於大多數物件來說,它就是它的記憶體位置(即它的 .WHERE)
.HOW- (HOW = 高階工作方式) 返回處理此物件的元類
.WHAT- 返回當前物件的型別物件