## 11.類&對象
### 11.1。 創建一個正方形
可以將類視為一個名稱捆綁在一起的變量和函數。 為了說明類,讓我們看一個不起眼的對象,叫做 Square。 正方形是一個非常簡單的幾何形狀,具有四個等長的邊,如何在 Ruby 程序中表示這個正方形? 現在,我們編寫一個稱為 Square 的空類,如下所示:
```rb
#square.rb
class Square
end
```
我們寫了一個空課。 `class`一詞表明我們正在編寫一個類的定義。 class 關鍵字之后是類的名稱,在本例中為`Square`。 必須注意,Ruby 中的類名稱必須以大寫字母 <sup class="footnote">[ [28](#_footnotedef_28 "View footnote.") ]</sup> 開頭。
一個正方形有四個邊,都長相同。 現在,我們將一個名為`side_length`的變量放入正方形。
```rb
#square.rb
class Square
attr_accessor :side_length
end
```
你可能會問`attr_accessor`是什么? 它代表`attribute accessor`,使你可以輕松獲取和設置`side_length`。 讓我們使用這個正方形類,看看它是如何工作的。 修改上面的代碼,如下所示:
```rb
#square.rb
class Square
attr_accessor :side_length
end
s1 = Square.new # creates a new square
s1.side_length = 5 # sets its side length
puts "Side length of s1 = #{s1.side_length}" # prints the side length
```
當你執行以上代碼時,這將是你得到的結果
```rb
Side length of s1 = 5
```
讓我們遍歷新添加的代碼,走線
```rb
s1 = Square.new
```
在上面的語句中,我們創建一個新的 Square 并將其參數存儲到名為`s1`的變量中。 可以使用`<class name>.new`創建一個新的類實例。 創建一個新的正方形后,我們現在可以使用點運算符“。”訪問其`side_length`。 因此,我們首先使用以下語句將`side_length`設置為五個單位
```rb
s1.side_length = 5
```
現在已經分配了`side_length`,我們可以將其用于任何目的。 現在,我們只需使用以下語句即可打印正方形的`side_length`:
```rb
puts "Side length of s1 = #{s1.side_length}"
```
### 11.2。 班級職能
我們有一個名為`Square`的類,它具有一個名為`side_length`的屬性。 使用`side_length`,我們可以找到正方形區域,其周長和對角線長度。 在此示例中,我將查找面積和周長。 因此,讓我們添加兩個函數來查找`area`和`perimeter`。 如下所示修改代碼(我將修改后的代碼保存在名為 [square_1.rb](code/square_1.rb) 的文件中)
```rb
#square_1.rb
class Square
attr_accessor :side_length
def area
@side_length * @side_length
end
def perimeter
4 * @side_length
end
end
```
在上面的代碼中,你看到我添加了兩個函數,一個名為`area`,另一個名為`perimeter`,分別計算并返回正方形的面積和周長。 這些函數與我們之前創建的任何其他函數非常相似,只是現在將其放置在類中。 讓我們編寫一些其他代碼來利用我們添加的新功能,只需修改程序,使其看起來像下面的代碼
```rb
#square_1.rb
class Square
attr_accessor :side_length
def area
@side_length * @side_length
end
def perimeter
4 * @side_length
end
end
a = Square.new
a.side_length = 5
puts "Area: #{a.area}"
puts "Perimeter: #{a.perimeter}"
```
運行示例,這是你將得到的輸出
```rb
Area: 25
Perimeter: 20
```
解釋很簡單。 在以下幾行
```rb
a = Square.new
a.side_length = 5
```
我們聲明了一個新的`Square`,并已將`side_length`分配為 5 個單位。 在下面的幾行中,我們只需打印出`a.area`和`a.perimeter`的值
```rb
puts "Area: #{a.area}"
puts "Perimeter: #{a.perimeter}"
```
了解我們如何嵌入“ a”的面積和周長的值(在上面的代碼中)。 如果你正在閱讀這本書,那么對你來說必須是新的一件事如下所示:
```rb
def area
@side_length * @side_length
end
```
我們知道 square 具有一個名為`side_length`的屬性,該屬性由語句`attr_accessor :side_length`定義,正如上面突出顯示的代碼所示,我們使用了`@side_length`而不是`side_length`,這是因為在類內部,類變量是 帶有`@`(at)符號的前綴。 這有助于我們在類變量和具有相同名稱的局部變量或函數之間進行標識。
### 11.3。 初始化程序或構造函數
在前面處理正方形的示例中,你是否想過當你說`s = Square.new`時會發生什么? 好吧,在這種情況下,將創建一個新的`Square`并將其放入變量中。 如果有人問天氣問題,當 Square 初始化時我們可以做什么? 答案是肯定的。 你所要做的就是將代碼放入稱為`initialize`的函數中,只要有`<class name>.new call`,就會調用此函數
看一下示例 [square2.rb](code/square2.rb) ,仔細看下面的代碼,我們定義了一個名為`initialize`的函數,該函數接受一個名為`side_length`的參數,其默認值為零。 如果指定了`side_length`,則它將平方類中的`@side_length`屬性設置為該值,否則`@side_length`的默認值為零。 在文本編輯器中輸入 [square2.rb](code/square2.rb) 并執行
```rb
#square_2.rb
class Square
attr_accessor :side_length
def initialize side_length = 0
@side_length = side_length
end
def area
@side_length * @side_length
end
def perimeter
4 * @side_length
end
end
s1 = Square.new 4
s2 = Square.new
s2.side_length = 5
puts "Area of s1 is #{s1.area} squnits"
puts "Perimeter of s2 is #{s2.perimeter} units"
```
輸出量
```rb
Area of s1 is 16 squnits
Perimeter of s2 is 20 units
```
在程序中集中于以下幾行
```rb
s1 = Square.new 4
s2 = Square.new
s2.side_length = 5
```
在第一行`s1 = Square.new 4`中,我們創建一個名為`s1`的正方形,其`@side_length`為 4 個單位。 在第二行`s2 = Square.new`中,我們創建一個名為`s2`的 Square,最初將其邊長(`@side_length`)設置為零個單位,僅在第三行`s2.side_length = 5`中將其`@side_length`設置為 5 個單位。
在其余的代碼中
```rb
puts "Area of s1 is #{s1.area} squnits"
puts "Peimeter of s2 is #{s2.perimeter} units"
```
我們打印`Square` `s1`的面積和`Square` `s2`的周長,以產生所需的輸出。
### 11.4。 未公開的實例變量
不需要使用`attr_accessor`公開實例變量,它可以隱藏,可以從函數中設置和調用,如圖所示
```rb
# unexposed_class_variables.rb
class Human
def set_name name
@name = name
end
def get_name
@name
end
end
a = Human.new
a.set_name "Karthik"
b = Human.new
b.set_name "Chubby"
puts "#{a.get_name} and #{b.get_name} are best friends."
```
Output
```rb
Karthik and Chubby are best friends.
```
在上面的示例中,注意`Human`類的(實例)確實使用了名為`@name`的變量,但你可以將其設置為`a.name =`,而不能像`a.name`那樣獲取它,因為它沒有暴露給 使用`attr_accessor`的外部世界。 因此,在 Ruby 類中,你可以在類中具有任意數量的隱藏變量,而無需外界對此有所了解。 那不漂亮嗎?
### 11.5。 私人方法
默認情況下,類中的方法或函數是公共的(可以在類范圍之外訪問),如果你不希望類外的程序訪問它們,則可以將其設為私有。 讓我們創建一個名為`Human`的類,并在其中放入一個私有方法,讓我們嘗試從該類外部訪問它,然后看看會發生什么。 輸入程序并執行
```rb
# private_method.rb
class Human
attr_accessor :name, :age
def tell_about_you
puts "Hello I am #{@name}. I am #{@age} years old"
end
private
def tell_a_secret
puts "I am not a human, I am a computer program. He! Hee!!"
end
end
h = Human.new
h.name = "Zigor"
h.age = 314567
h.tell_about_you
h.tell_a_secret # this wont work
```
上面的程序在執行時產生以下結果
```rb
Hello I am Zigor. I am 314567 years old
human.rb:20: private method `tell_a_secret' called for #<Human:0xb7538678 @name="Zigor", @age=314567> (NoMethodError)
```
查看類中的函數`tell_a_secret`,將其放置在關鍵字`private`下,這將使其無法從類外部訪問。 請注意,當我們調用方法`tell_a_secret`時,該行拋出一個錯誤,實際上,它說的是無方法錯誤(`NoMethodError`),這意味著被調用的方法在類中不存在。 這并不意味著計算機在撒謊,而是在安全地保密。
在編程時,你只讓程序的某些部分對其他人可見,這有助于使界面保持簡單,并僅向用戶提供他們真正需要編寫代碼的資源,這種隱藏不必要的東西使編程變得不復雜。
有人可能會問,如果沒有辦法訪問私有方法,那為什么我們需要它呢? 好了,你可以在下面的示例中間接訪問它。 輸入并執行
```rb
# private_method_1.rb
class Human
attr_accessor :name, :age
def tell_about_you
puts "Hello I am #{@name}. I am #{@age} years old"
end
def confess
tell_a_secret
end
private
def tell_a_secret
puts "I am not a human, I am a computer program. He! Hee!!"
end
end
h = Human.new
h.name = "Zigor"
h.age = 314567
h.tell_about_you
h.confess
```
結果就是這樣
```rb
Hello I am Zigor. I am 314567 years old
I am not a human, I am a computer program. He! Hee!!
```
好好看看 confess 方法,在其中我們稱為私有方法`tell_a_secret`,因此當我們甚至在類(`h.confess`)之外調用 confess 時,公共的`confess`方法也稱為私有方法,因為`confess`在`Human`類中,它可以無障礙地訪問`Human`中的任何私有方法,因此程序可以完美執行。
### 11.6。 類變量和方法
到現在為止,我們已經學會了創建一個類,我們知道一個類可以具有某些屬性,例如人類可能具有諸如姓名,年齡等屬性....我們知道該類可以包含一些可以被以下對象調用的功能 變量,它是類的實例。 現在,如果我們要調用一個函數或一個類而不聲明一個變量即該類的實例怎么辦? 可以在不聲明屬于類類型的實例變量的情況下調用的函數稱為類方法。 那些無需使用類的實例變量即可訪問的變量稱為類變量。
讓我們看一個演示類變量和方法的程序。 在下面的程序 [class_var_methods.rb](code/class_var_methods.rb) 中,我將創建一個名為`Robot`的類。 它將具有一個名為`@@robot_count`的類變量,該變量將跟蹤創建的機器人數量。 由于它是一個類變量,我們通過在其前面使用兩個@符號將其指示給計算機,因此在程序中,我們將其表示為`@@robot_count`。 我們創建了一個名為`robots_created`的函數,該函數將返回創建的機器人數量。 注意(在下面的程序中)函數`robots_created`編寫為`self.robots_created`,關鍵字`self`告訴計算機可以在不聲明實例對象的情況下調用此函數。
在文本編輯器中輸入 class_var_method.rb 下面顯示的程序并執行
```rb
# class_var_methods.rb
class Robot
def initialize
if defined?(@@robot_count)
@@robot_count += 1
else
@@robot_count = 1
end
end
def self.robots_created
@@robot_count
end
end
r1 = Robot.new
r2 = Robot.new
puts "Created #{Robot.robots_created} robots"
r3, r4, r5 = Robot.new, Robot.new, Robot.new
puts "Created #{Robot.robots_created} robots"
```
執行以上程序時,將得到以下結果
```rb
Created 2 robots
Created 5 robots
```
讓我們看看`initialize`方法,并分析我們如何跟蹤已創建的機器人數量。 在語句中創建第一個機器人時
```rb
r1 = Robot.new
```
由于第一次沒有定義變量`@@robot_count`,因此程序控制轉到`initialize`方法,因此下面的`if`語句中的條件
```rb
if defined?(@@robot_count)
@@robot_count += 1
else
@@robot_count = 1
end
```
失敗,代碼轉到 else 部分,`@@robot_count = 1`定義了變量`@@robot_count`并初始化為值 1。
在第二條語句中,我們使用以下命令創建名為 r2 的機器人
```rb
r2 = Robot.new
```
控件再次進入`initialize`方法,`if`語句通過,因為在創建`r1`時已經定義了`@@robot_count`,現在`@@robot_count`遞增 1,現在變為 2。
接下來是我們稱為`Robot.robots_created`的`puts`語句,該語句僅返回`@@robot_count`,因此將輸出 2。 以下語句中的下一步:
```rb
r3, r4, r5 = Robot.new, Robot.new, Robot.new
```
我們創建了三個新的機器人`r3`,`r4`和`r5`。 現在``@@robot_count`將增加到 5。在下一個`puts`語句中,將打印結果。 故事的寓意是這樣,課堂方法有一個`self`。 (自點)之前,類變量前面有兩個@(`@@`)。
很好,希望一切對讀者都適用。 為什么現在我們對`robot_count`變量使用`attr_reader`,以便我們的程序變得簡化,如下所示。 輸入并執行。
```rb
# attr_for_classvar.rb
# this program dosent work
class Robot
attr_reader :robot_count
def initialize
if defined?(@@robot_count)
@@robot_count += 1
else
@@robot_count = 1
end
end
end
r1 = Robot.new
r2 = Robot.new
puts "Created #{Robot.robot_count} robots"
r3, r4, r5 = Robot.new, Robot.new, Robot.new
puts "Created #{Robot.robot_count} robots"
```
你得到了什么結果? 可以將`attr_reader`用于類變量嗎?
### 11.7。 遺產
我們從猴子進化而來。 黑猩猩看起來像我們,我們都有許多共同的特征,我們擁有許多與黑猩猩相似的屬性,它們是如此相似,以至于黑猩猩在我們面前被送入太空,以了解零重力對猴子身體的影響。 只有當科學家感到安全時,他們才向人類發送 <sup class="footnote">[ [29](#_footnotedef_29 "View footnote.") ]</sup> 。 當人類從猴子進化而來時,他從猴子那里繼承了很多東西,例如我們看起來像猴子,不相信嗎? 只是去站在鏡子前面!
好的,在編程世界中,我們有一個稱為繼承的事物,其中一個類可以具有另一類的屬性,而只需很少(或有時是極端的)變化。 讓我告訴你一個數學真理,“正方形是所有邊都相等的矩形”,不是嗎? 所有正方形都是矩形,但并非所有矩形都是正方形。 我們將使用這些東西來編寫我們的下一個程序 [Inheritance.rb](code/inheritance.rb) 。 在文本編輯器中編寫程序并執行。
```rb
# inheritance.rb
class Rectangle
attr_accessor :length, :width
def initialize length, width
@length = length
@width = width
end
def area
@length * @width
end
def perimeter
2 * (@length + @width)
end
end
class Square < Rectangle
def initialize length
@width = @length = length
end
def side_length
@width
end
def side_length=(length)
@width = @length = length
end
end
s = Square.new 5
puts "Perimeter of the square s is #{s.perimeter}"
r = Rectangle.new 3, 5
puts "Area of rectangle r is #{r.area}"
```
執行后,上面的程序將產生以下結果
```rb
Perimeter of the square s is 20
Area of rectangle r is 15
```
仔細閱讀程序,我們定義了一個名為`Rectangle`的類,它具有兩個屬性,即`@length`和`@width`。 在語句`r = Rectangle.new 3, 5`中初始化它時,我們傳遞了這兩個參數。 當調用`area`時,將返回這兩個屬性的乘積,當使用某些天才公式調用其`perimeter`時,將計算并返回周長。 然后,我們定義一個名為`Square`的類,該類繼承`Rectangle`的屬性。 要說類`Square`繼承了`Rectangle`,我們使用`<`(小于)符號,如下所示
```rb
class Square < Rectangle
```
看看`Square`類中的`initialize`方法,它僅使用一個參數`length`,它用于設置屬性`@length`和`@width`的值。 由于`Square`繼承了`Rectangle`類,因此默認情況下會獲取`Rectangle`的所有屬性和方法。 設置 Square 的`@width`和`@height`之后,我們現在可以調用 Square 的`area`和`perimeter`函數,就像我們對`Rectangle`所做的那樣。
### 11.8。 覆蓋方法
我們已經看到,類可以從其基類繼承屬性和方法。 假設我們有一個類`A`,它是`B`的父類(即 B 繼承了 A),現在場景是`B`中定義了一個方法,該方法與`A`中的方法同名 ]。 當我們創建類型為`B`的實例變量并調用該方法`name`時,它將檢查該方法是否在類`B`中存在,如果是,則將執行該方法,如果在`B`中未找到該方法, 然后 Ruby 解釋器將在類`A`中對其進行檢查,如果發現其已執行,則引發`NoMethodError` <sup class="footnote">[ [30](#_footnotedef_30 "View footnote.") ]</sup> 。
為了清楚起見,請看一個示例,鍵入并執行 [overlay_methods.rb](code/override_methods.rb)
```rb
#!/usr/bin/ruby
# override_methods.rb
class A
def belongs_to
puts "I belong to class A"
end
def another_method
puts "Just another method in class A"
end
end
class B < A
def another_method
puts "Just another method in class B"
end
end
a = A.new
b = B.new
a.belongs_to
a.another_method
b.belongs_to # This is not overriden so method in class A is called
b.another_method # This is overridden so method in class B is called
```
結果
```rb
I belong to class A
Just another method in class A
I belong to class A
Just another method in class B
```
看一下結果。 調用`a.belongs_to`時,程序將按照在`A`類中定義的輸出`I belong to class A`。 當調用`a.another_method`時,我們看到程序將打印出`Just another method in class A`,如在`A`類中定義的那樣。 當`b.belongs_to is`調用該程序時,由于`B`類中沒有`belongs_to`方法,因此再次打印出`I belong to class A`,因此將調用父方法。 參見調用`b.another_method`時的情況,程序會打印出`Just another method in class B`而不顯示`Just another method in class A`,因為`B`的范圍為`another_method`,因此無需在`A`類中查找該方法。
我們將把覆蓋概念更進一步,知道 Ruby 中的一切都是對象。 Ruby 純粹是一種面向對象的編程語言。 拍攝你的 irb 并輸入以下內容
```rb
>> "Something".class
=> String
>> 1.class
=> Integer
>> 3.14278.class
=> Float
```
在 Ruby 中,只要在對象后放置`.class`,它就會返回該對象所屬的類。 因此,我們看到 1、2、3…....等數字屬于`Integer`類。 讓我們重寫其關鍵方法`+`。 Ruby 中使用加號將兩個數字相加。 在 irb 中嘗試這些示例
```rb
>> 1 + 2
=> 3
>> 478 + 90
=> 568
```
因此,我們看到當有一個整數時,后面跟一個加號,另一個 Integer Ruby 解釋器將這兩個 Integer 相加,并作為結果返回。 現在讓我們弄亂加號。 看一下 [overlay_methods_1.rb](code/override_methods_1.rb) ,在文本編輯器中將其鍵入并執行。
```rb
# override_methods_2.rb
class Integer
def + a
416
end
end
puts 3 + 5
puts 7 + 12
```
Result
```rb
416
416
```
看結果,你不驚訝嗎? 當添加 3 和 5 時,結果為 416,添加 7 和 12 時,結果再次為 416。看一下 Fixnum 類中的代碼。 為了使你的閱讀更加方便,以下是代碼:
```rb
def + a
416
end
```
在其中我們重新定義了 Integer 類中的方法`+`。 在其中,我們已經說過無論 a 的值是什么(即`+`符號右側的數字),我們都必須返回 416 的值,因此 Ruby 解釋器會簡單地服從它。
Integer 是 Ruby 的核心類,在許多編程語言(例如 Java)中,它都不愿意修改核心類,但 Ruby 允許。 許多撰寫過有關 Ruby 的書的作者和編程專家都稱此 Ruby 功能為一項危險功能,這確實是危險的,如果你確實在 Ruby 中修改了一個重要的類并且如果我們的代碼被深埋在項目中,那么有時它可以 會導致程序中出現嚴重的邏輯錯誤,有時可能會浪費大量資源,因為人們需要埋頭調試代碼。 因此,在覆蓋類中的方法之前,請先考慮一下,然后在確實有必要的情況下進行一些改進。
### 11.9。 超級功能
見下面的程序
```rb
#!/usr/bin/ruby
# class_super.rb
class Rectangle
def set_dimension length, breadth
@length, @breadth = length, breadth
end
def area
@length * @breadth
end
end
class Square < Rectangle
def set_dimension side_length
super side_length, side_length
end
end
square = Square.new
square.set_dimension 7
puts "Area: #{square.area}"
```
Output
```rb
Area: 49
```
在程序中,你會看到一個`Rectangle`類,在其中你會看到一個名為`set_dimension`的函數,如下所示。 此函數接收兩個參數`length`和`breadth`,它們在此行`@length, @breadth = length, breadth`中分配給類變量`@length`和`@breadth`
```rb
class Rectangle
def set_dimension length, breadth
@length, @breadth = length, breadth
end
def area
@length * @breadth
end
end
```
現在查看類`Square`。 `Square`繼承了`Rectangle`(長度和寬度相等),但并非相反。 現在注意下面的代碼
```rb
class Square < Rectangle
def set_dimension side_length
super side_length, side_length
end
end
```
你可以看到`Square`類具有自己的`set_dimension`方法,現在看看它具有什么,它有了一個新東西,看看顯示`super side_length, side_length`的行,在這里我們稱為`super`的新方法。 `super`是一種特殊的方法,如果在`set_dimension`中調用它,它將查看父類是否具有同名的方法,如果是,則調用該方法。 因此,此處`super`將在`Rectangle`中稱為`set_dimension`,并將`side_length`傳遞到`length`,從而將其設置為`@length`,將`side_length`傳遞到`breadth`,從而將其分別設置為`@breadth` 。
`@length`和`@breadth`相等的矩形是正方形! 不是嗎 認為!!!
### 11.10。 私人,公共和繼承保護
由于我們在談論繼承,因此現在更好了,因為我們看到了公共,私有和受保護方法的作用。 要了解它們,請鍵入 [public_private_protected_in_inheritance.rb](code/public_private_protected_in_inheritance.rb) 并執行它。
```rb
# public_private_protected_in_inheritance.rb
class A
def public_method
puts "Class A public method"
end
private
def private_method
puts "Class A private method"
end
protected
def protected_method
puts "Class A protected method"
end
end
class B < A
def get_class_a_protected_method
protected_method # implicit call
end
def get_class_a_private_method
private_method # implicit call
end
end
class C < A
def get_class_a_protected_method
self.protected_method # explicit call
end
def get_class_a_private_method
self.private_method # explicit call
end
end
a = A.new
a.public_method
# a.protected_method
# a.protected_method
b = B.new
b.get_class_a_protected_method
b.get_class_a_private_method
c = C.new
c.get_class_a_protected_method
# c.get_class_a_private_method
```
Output
```rb
Class A public method
Class A protected method
Class A private method
Class A protected method
```
你將獲得如上所示的輸出。 現在讓我們分析程序。有一個名為`A`的類,它具有如下所示的公共方法
```rb
class A
def public_method
puts "Class A public method"
end
……….
end
```
如圖所示的私有方法
```rb
class A
...
private
def private_method
puts "Class A private method"
end
...
end
```
和受保護的方法
```rb
class A
...
protected
def protected_method
puts "Class A protected method"
end
end
```
如你從上面的示例中看到的代碼,如果你輸入關鍵字`private`,則在其下鍵入的內容將變為私有,對于受保護的內容也是如此。 我本可以編寫如下所示的 public 方法
```rb
class A
public
def public_method
puts "Class A public method"
end
……….
end
```
但是默認情況下,如果方法不屬于私有方法或受保護方法,則該方法為公共方法,因此在此方法上方未包含任何內容。 現在讓我們看一下 B 類
```rb
class B < A
def get_class_a_protected_method
protected_method # implicit call
end
def get_class_a_private_method
private_method # implicit call
end
end
```
查看突出顯示的代碼。 在`get_class_a_protected_method`和`get_class_a_private_method`中,我們以隱式方式調用`protected_method`和`private_method`,并且在執行以下代碼時
```rb
b = B.new
b.get_class_a_protected_method
b.get_class_a_private_method
```
他們完美地工作。 現在讓我們看 C 類,
```rb
class C < A
def get_class_a_protected_method
self.protected_method # explicit call
end
def get_class_a_private_method
self.private_method # explicit call
end
end
```
如你在上面的代碼中看到的那樣,在`get_class_a_protected_method`和`get_class_a_private_method`中,如上面的代碼段所示,顯式調用了 protected_method 和 private_method,現在在下面的這段代碼中
```rb
c = C.new
c.get_class_a_protected_method
# c.get_class_a_private_method
```
`c.get_class_a_private_method`將引發`NoMethodError`(無方法錯誤)異常,而`c.get_class_a_protected_method`將平穩運行。 因此,我們可以很清楚地看到,在顯式調用中,無法訪問 Parent 類的私有方法。 我鼓勵人們取消注釋上面的注釋代碼并執行它,并自己經歷錯誤。
值得一提的是,在一種情況下,即使在顯式調用期間,方法的私有狀態也不起作用。 看下面的代碼,鍵入并執行它。
```rb
# private_attribute_writer.rb
class Parent
private
# we have a attribute writer private method
def private_method= some_val
puts some_val
end
end
class Child < Parent
def set_some_val
self.private_method = "I know your secret!"
end
end
Child.new.set_some_val
```
Output
```rb
I know your secret!
```
令人驚訝的是,當執行`Child.new.set_some_val`時,實際上它以顯式方式調用了 Parent 類中的`private_method`,但執行時卻沒有大驚小怪。 這是因為如上代碼所示,Parent 類中的代碼`private_method`是屬性編寫器,即以`=`符號結尾。 這是 Ruby 中的特例。
### 11.11。 延伸班
Ruby 允許程序員(幾乎)以任何想要的方式擴展預先存在的類,無論這些類是你編寫的還是捆綁到 Ruby 語言本身中都沒有關系。 在下面的示例中,我們將擴展 Integer 類以適合我們的需求。 在文本編輯器中鍵入程序并執行
```rb
# extending_class_1.rb
class Integer
def minute
to_s.to_i * 60
end
def hour
to_s.to_i.minute * 60
end
def day
to_s.to_i.hour * 24
end
def week
to_s.to_i.day * 7
end
end
puts Time.now + 2.week
```
Result
```rb
2018-10-10 10:27:37 +0530
```
該程序將當前時間與當前秒精確地相差 2 周,請注意,我們通過以下語句進行操作:
```rb
puts Time.now + 2.week
```
`Time.now`獲取當前的`Time`實例,并在其中添加`2.week`。 實際上,本機`Integer`類中沒有名為`week`的方法,但是在程序中我們看到定義了`Integer`類,該類的方法名為`week`,當其調用時返回一周中的秒數 可以將其添加到時間對象或從中減去以獲取過去和將來的時間。
你可以類似的方式在 Ruby 中擴展任何類,幾乎可以重寫任何方法。 當然,一些程序員將其視為威脅,因為一些意外更改可能會在你的代碼中引入錯誤,但是,如果你真的喜歡 Ruby,則無關緊要。
### 11.12。 反射
反射是計算機程序可以自行分析并隨時進行修改的過程。 在接下來的頁面中,我們將僅從頭開始。
我們將在 irb 中嘗試這些示例,因此在你的終端中鍵入 irb –simple-prompt。 在下面的 irb 提示中,我聲明了一個字符串變量`a`并將其設置為值`“Some string”`
```rb
>> a = "Some string"
=> "Some string"
```
現在讓我們看看可以使用變量`a`可用的方法。 為此,請在 irb 中鍵入`a.methods`
```rb
>> a.methods
=> ["upcase!", "zip", "find_index", "between?", "to_f", "minmax", "lines", "sub", "methods", "send", "replace", "empty?", "group_by", "squeeze", "crypt", "gsub!", "taint", "to_enum", "instance_variable_defined?", "match", "downcase!", "take", "find_all", "min_by", "bytes", "entries", "gsub", "singleton_methods", "instance_eval", "to_str", "first", "chop!", "enum_for", "intern", "nil?", "succ", "capitalize!", "take_while", "select", "max_by", "chars", "tr!", "protected_methods", "instance_exec", "sort", "chop", "tainted?", "dump", "include?", "untaint", "each_slice", "instance_of?", "chomp!", "swapcase!", "drop", "equal?", "reject", "hex", "minmax_by", "sum", "hash", "private_methods", "all?", "tr_s!", "sort_by", "chomp", "upcase", "start_with?", "unpack", "succ!", "enum_slice", "kind_of?", "strip!", "freeze", "drop_while", "eql?", "next", "collect", "oct", "id", "slice", "casecmp", "grep", "strip", "any?", "delete!", "public_methods", "end_with?", "downcase", "%", "is_a?", "scan", "lstrip!", "each_cons", "cycle", "map", "member?", "tap", "type", "*", "split", "insert", "each_with_index", "+", "count", "lstrip", "one?", "squeeze!", "instance_variables", "__id__", "frozen?", "capitalize", "next!", "each_line", "rstrip!", "to_a", "enum_cons", "ljust", "respond_to?", "upto", "display", "each", "inject", "tr", "method", "slice!", "class", "reverse", "length", "enum_with_index", "rpartition", "rstrip", "<=>", "none?", "instance_variable_get", "find", "==", "swapcase", "__send__", "===", "min", "each_byte", "extend", "to_s", "rjust", "index", ">=", "size", "reduce", "tr_s", "<=", "clone", "reverse_each", "to_sym", "bytesize", "=~", "instance_variable_set", "<", "detect", "max", "each_char", ">", "to_i", "center", "inspect", "[]", "reverse!", "rindex", "partition", "delete", "[]=", "concat", "sub!", "dup", "object_id", "<<"]
```
如你所見,`a.methods`返回可以在`String`上使用的方法,即函數。 接下來,我們將嘗試找出`a`屬于什么類或類型。 我們當然知道它屬于`String`類型,但是為了學習將`a.class`類型編程為 irb
```rb
>> a.class
=> String
```
并忠實地返回 a 屬于`String`類型。
很好,我們已經看到了一些有關反射的東西。 為了更好地理解它,讓我們定義我們自己的類,看看反射如何作用于它。 在文本編輯器中輸入以下程序 [Reflection.rb](code/reflection.rb) 并執行它。
```rb
# reflection.rb
class Someclass
attr_accessor :a, :b
private
# A dummy private method
def private_method
end
protected
# A dummy protected method
def protected_method
end
public
# A dummy public method
def public_method
end
end
something = Someclass.new
something.a = 'a'
something.b = 123
puts "something belongs to #{something.class}"
puts
puts "something has the following instance variables:"
puts something.instance_variables.join(', ')
puts
puts "something has the following methods:"
puts something.methods.join(', ')
puts
puts "something has the following public methods:"
puts something.public_methods.join(', ')
puts
puts "something has the following private methods:"
puts something.private_methods.join(', ')
puts
puts "something has the following protected methods:"
puts something.protected_methods.join(', ')
```
Output
```rb
something belongs to Someclass
something has the following instance variables:
@a, @b
something has the following methods:
inspect, protected_method, tap, clone, public_methods, __send__, object_id, instance_variable_defined?, equal?, freeze, extend, send, methods, public_method, hash, dup, to_enum, instance_variables, eql?, a, instance_eval, id, singleton_methods, a=, taint, enum_for, frozen?, instance_variable_get, instance_of?, display, to_a, method, b, type, instance_exec, protected_methods, ==, b=, ===, instance_variable_set, kind_of?, respond_to?, to_s, class, __id__, tainted?, =~, private_methods, untaint, nil?, is_a?
something has the following public methods:
inspect, tap, clone, public_methods, __send__, object_id, instance_variable_defined?, equal?, freeze, extend, send, methods, public_method, hash, dup, to_enum, instance_variables, eql?, a, instance_eval, id, singleton_methods, a=, taint, enum_for, frozen?, instance_variable_get, instance_of?, display, to_a, method, b, type, instance_exec, protected_methods, ==, b=, ===, instance_variable_set, kind_of?, respond_to?, to_s, class, __id__, tainted?, =~, private_methods, untaint, nil?, is_a?
something has the following private methods:
exit!, chomp!, initialize, fail, print, binding, split, Array, format, chop, iterator?, catch, readlines, trap, remove_instance_variable, getc, singleton_method_added, caller, putc, autoload?, proc, chomp, block_given?, throw, p, sub!, loop, syscall, trace_var, exec, Integer, callcc, puts, initialize_copy, load, singleton_method_removed, exit, srand, lambda, global_variables, gsub!, untrace_var, open, `, system, Float, method_missing, singleton_method_undefined, sub, abort, gets, require, rand, test, warn, eval, local_variables, chop!, scan, raise, printf, set_trace_func, private_method, fork, String, select, sleep, gsub, sprintf, autoload, readline, at_exit, __method__
something has the following protected methods:
protected_method
```
如上所示,你必須具有相當大的輸出。 現在讓我們遍歷代碼。 首先,我們定義一個名為`Someclass`的類,其中有兩個屬性`a`和`b`。 我們有一個稱為`private_method`的私有方法,一個名為`protected_method`的受保護方法以及名為`public_method`的公共方法。
定義完類后,我們創建一個類型為`Someclass`的名為`something`的變量,并在以下幾行中為其屬性賦值
```rb
something = Someclass.new
something.a = 'a'
something.b = 123
```
接下來,我們要求 ruby 解釋器使用忠實執行的以下語句`puts "something belongs to #{something.class}"`打印變量`something`的類,因此得到以下輸出:
```rb
something belongs to Someclass
```
接下來,我們想知道類型為`Someclass`的對象`something`是否具有任何實例變量。 要知道它,我們使用以下代碼:
```rb
puts "something has the following instance variables:"
puts something.instance_variables.join(', ')
```
為此,我們得到以下輸出
```rb
something has the following instance variables:
@a, @b
```
接下來,我們想知道有什么方法可以使用。 要知道我們可以使用`methods`函數,因此我們編寫以下代碼:
```rb
puts "something has the following methods:"
puts something.methods.join(', ')
```
在上面的代碼中`something.methods`返回一個方法數組,必須將其轉換為由`join`方法完成的字符串。 數組的元素由傳遞給`join`方法的 String 連接。 請注意,還有比我們定義的方法更多的方法,這是因為甚至`Someclass`的類型也都是`Object` <sup class="footnote">[ [31](#_footnotedef_31 "View footnote.") ]</sup> 類型,它本身也有許多方法。 在 Ruby 中,一切都是對象。
任何 Object 的方法和 public_methods 都會返回相同的結果。 所以我們將跳過關于這些的討論
```rb
puts "something has the following public methods:"
puts something.public_methods.join(', ')
```
陳述
接下來,我們想知道`Someclass`中的私有,公共和受保護方法是什么,由于`something`屬于`Someclass`,因此可以使用`private_methods`函數獲得私有方法,因此通過給出以下語句
```rb
puts "something has the following private methods:"
puts something.private_methods.join(', ')
```
我們可以在某些類中獲得私有方法。
通過使用`protected_methods`函數也可以得到類似的受保護方法,由于我的懶惰,我將不再討論。
### 11.13。 封裝形式
你可能在生活中的某個時間點服用了膠囊片劑。 在其中,藥物包裝在明膠膠囊內。 當你用水服用時,它會滑到你的胃中,在那里水分解出明膠層,釋放出其中的藥物,從而治愈你的身體疾病。 如果你嘗試在沒有膠囊的情況下吞服藥物,那將是一種痛苦的經歷。
現代編程語言以類似的方式允許你隱藏不需要的細節,并讓你的同伴程序員僅查看所需的細節。 這項技術稱為封裝,如果正確實施,則會產生清晰的代碼,并且易于使用。
封裝的另一個很好的例子是你的汽車。 在引擎蓋下,你的汽車有成千上萬個可以這種方式旋轉的零件,但是你要做的就是打開鑰匙并操作方向盤和踏板以獲得駕駛體驗。 你無需理會引擎蓋內部發生的事情。
讓我們看一個小例子,它將向我們解釋封裝的工作原理。 在文本編輯器中鍵入程序 [encapsulation.rb](code/encapsulation.rb) 并執行它。
```rb
# encapsulation.rb
class Human
attr_reader :firstname, :lastname
def name=(name)
@firstname, @lastname = name.split
end
end
guy = Human.new
guy.name = "Ramanuja Iyengaar"
puts "First name: #{guy.firstname}"
puts "Last name: #{guy.lastname}"
```
Output
```rb
First name: Ramanuja
Last name: Iyengaar
```
這樣我們得到的人的名字叫 Ramanuja,姓氏叫 Iyengar。 由于以下陳述,將這兩行打印出來
```rb
puts "First name: #{guy.firstname}"
puts "Last name: #{guy.lastname}"
```
在這些語句之前,請參見兩行。 首先,通過編寫`guy = Human.new`聲明一個類型為`Human`的名為`guy`的新變量,接著設置`guy.name = "Ramanuja Iyengaar"``,但是在第一個`puts`語句中,我們將調用`guy.firstname`,在下一個中,我們將`guy.lastname`,我們得到了答案。 這是因為在程序內的`name`方法中,請參見此代碼
```rb
def name=(name)
@firstname, @lastname = name.split
end
```
我們將其拆分,并使用以下代碼將空格前的單詞分配為`@firstname`,將空格后的單詞分配為`@lastname`:
```rb
@firstname, @lastname = name.split
```
因此,當我們調用`guy.firstname`和`guy.lastname`時,它們會如實打印。 請注意,在類外,我們從未設置過`@first_name`和`@last_name`,它們是完全由我們封裝的。
有人可能想知道`attr_reader :firstname, :lastname`語句的作用是什么? 它聲明了兩個變量`@first_name`和`@last_name`。 `attr_reader`表示這兩個變量只能由類外部的程序讀取,換句話說,如果我們嘗試設置`guy.first_name = “Ramanuja”`,則 Ruby 解釋器將拋出錯誤。
### 11.14。 多態性
Poly 表示很多,而 morphis 表示形式。 我認為它是希臘語還是拉丁語,誰在乎? 在編程語言中,你可以使用一件事情來做很多事情,下面舉幾個例子。 讓我們采取謙虛的加號。 當我們取`“Hello ”`和`“World!”`并在它們之間加一個加號時,輸出為`“Hello World!”`。 在技??術交流中,我們稱這種串聯(連接在一起)。 這是 irb 示例:
```rb
>> "Hello " + "World!"
=> "Hello World!"
```
現在讓我們在數字上使用此加號。 現在,我們將其停留在 134 和 97 之間。執行此操作時,答案為 231,而不是 13497。為什么? 這是因為加號被卡在不同事物之間時,訓練它可以做不同的事情。
```rb
>> 134 + 97
=> 231
```
當你將其插入字符串之間時,它們會連接在一起;當你將其插入數字之間時,則會添加它們。 因此,操作員會根據情況采取多種形式或進行不同的操作。
以類似的方式,如果將字符串乘以數字會發生什么。 好吧,如下所示
```rb
>> "Hello" * 5
=> "HelloHelloHelloHelloHello"
```
我們看到字符串被打印的次數。 因此,將“ Hello”乘以 5 可打印“ Hello”五次。 在下一個示例中,我們將值 6 分配給名為 hello 的變量,并將其乘以 5
```rb
>> hello = 6
=> 6
>> hello * 5
=> 30
```
由于`hello`是帶有數字的變量,因此將其與數字相乘會得到一個數字。 因此,你甚至可以看到乘法運算符根據情況采用多種形式或不同的函數。 就像這樣,一個警察在家時對家人友善,當他受到轟擊時,他的舉止卻有所不同。
長度運算符/函數以類似的方式,當你找出字符串的長度時,它會告訴其中的字符數。
```rb
>> "some text".length
=> 9
```
當你找到數組的長度時,它會告訴該數組具有的元素數。
```rb
>> [5, 7, "some text", Time.now].length
=> 4
```
因此我們看到在 Ruby 中,一件事情可以做不同的事情,就像現實世界中的對象 <sup class="footnote">[ [32](#_footnotedef_32 "View footnote.") ]</sup> 一樣。
### 11.15。 類常量
就像任何其他編程語言一樣,在 Ruby 中的類中可以有一個常量值。 讓我們開始行動,看看下面的程序 [class_constant.rb](code/class_constant.rb) ,其源代碼是這樣的
```rb
#!/usr/bin/ruby
# class_constant.rb
class Something
Const = 25
end
puts Something::Const
```
Output
```rb
25
```
注意在`Something`類中的代碼段,你會看到`Const = 25`語句。 如果你讀得很好,你可能會意識到 Ruby 中的常量以大寫字母開頭。 在類`Something`中,我們聲明了一個常量名稱`Const`并將其分配給 25。
請注意,語句`puts Something::Const`和`puts`用于打印拋出的幾乎所有內容。 在這里,我們拋出`Something::Const`,它忠實地打印出恒定值。 因此,可以通過`<class_name>::<constant_name>`訪問類常量,這就是訪問類常量的方式。 讓我們看看類實例如何訪問類常量。 鍵入程序 class_constant_1.rb
```rb
#!/usr/bin/ruby
# class_constant.rb
class Something
Const = 25
end
puts Something::Const
```
Output
```rb
25
class_constant_1.rb:10: undefined method `Const' for #<Something:0xb745eb58> (NoMethodError)
```
因此,在上面的程序中,我們聲明了一個類別為`Something`的變量`s`。 在`puts s.Const`行中,我們嘗試通過其實例變量 s 訪問某個內部的常量值,并且出現 No Method Error,或者 Ruby 解釋器認為`Const`是一種方法,因為我們使用了`s.Const`。 要解決此問題,你可以編寫一個名為`Const`的方法,然后按 [class_constant_2.rb](code/class_constant_2.rb) 中所示的方法進行調用
```rb
include::code/class_constant_2.rb
```
Output
```rb
25
25
```
因此,定義方法 <sup class="footnote">[ [33](#_footnotedef_33 "View footnote.") ]</sup> 并從中返回`Const`解決了該問題。
有人可能會認為可以使用實例變量通過使用雙冒號(::)而不是 [class_constant_3.rb](code/class_constant_3.rb) 中所示的點運算符來訪問類常量值,正如你從其輸出中看到的那樣,它將無法正常工作
```rb
#!/usr/bin/ruby
# class_constant_3.rb
class Something
Const = 25
def Const
Const
end
end
puts Something::Const
s = Something.new
puts s::Const
```
Output
```rb
25
class_constant_3.rb:14: #<Something:0xb74029fc> is not a class/module (TypeError)
```
### 11.16。 功能別名
有時你可能已經編寫了一個函數,并且可能希望將其重命名為某種東西,那時候該怎么辦? 如果你在許多地方都使用過該功能,并且必須重命名,那么你必須在許多地方不斷更改名稱。 幸運的是,ruby 提供了一個名為`alias`的關鍵字,你可以使用該關鍵字為函數設置另一個名稱。 看一下下面的程序,尤其是這一行`alias :shout :make_noise`。
```rb
# function_alias.rb
class Something
def make_noise
puts "AAAAAAAAAAAAAAHHHHHHHHHHHHHH"
end
alias :shout :make_noise
end
Something.new.shout
```
Output
```rb
AAAAAAAAAAAAAAHHHHHHHHHHHHHH
```
如你所見,我們將其稱為`Something.new.shout`,并且由于我們已將`make_noise`別名為`shout`,因此將調用`make_noise`。
### 11.17。 匿名類
類可以沒有名稱,這些類稱為匿名類。 查看下面的示例,鍵入并執行:
```rb
# anonymous_class.rb
person = Class.new do
def say_hi
'Hi'
end
end.new
puts person.say_hi
puts person.class
```
Output
```rb
Hi
#<Class:0x0000000002696840>
```
因此,讓我們看看它是如何工作的。 首先,你有一個作為類實例的變量`person`,我們將其分配給`person = Person.new`而不是`person = Person.new`,如下所示。 也就是說,我們正在動態創建一個沒有任何名稱的新類。 因此它是匿名的。
```rb
person = Class.new do **(1)**
......
end.new **(2)**
```
| **1** | 創建一個匿名類。 |
| **2** | 一個匿名類,在其`end`應該已經用`new`初始化之后 |
如果你可以從上面的代碼中立即看到匿名類的定義,則必須使用`.new`立即對其進行初始化。 這也使匿名類僅堅持一個變量。 現在,我們將所需的內容填寫到匿名類中,如下所示:
```rb
person = Class.new do
def say_hi
'Hi'
end
end.new
```
因此,當我們調用`person.say_hi`時,它返回要打印的`'Hi'`,但是當我們調用`person.class`而不是打印類名時,它會打印出類似這樣的內容`#<Class:0x0000000002696840>` <sup class="footnote">[ [34](#_footnotedef_34 "View footnote.") ]</sup> 。 由于該類沒有名稱,因此沒有要打印的名稱。
如果你在下面的 irb 中看到我編寫的代碼,我們可以看到調用`a.class`時會顯示`A`,即類名,但在上述情況下,我們沒有名稱。
```rb
>> class A; end
=> nil
>> a = A.new
>> a.class
=> A
```
我認為`#<Class:0x0000000002696840>`中的`0x…?.`代表類存儲在內存中的地址位置。
- 前言
- 紅寶石
- 先決條件
- 1.安裝 Ruby
- 2.在線資源
- 3.入門
- 4.比較與邏輯
- 5.循環
- 6.數組
- 7.哈希和符號
- 8.范圍
- 9.功能
- 10.可變范圍
- 11.類&對象
- 12.安全導航
- 13.打破大型程序
- 14.結構和 OpenStruct
- 15. Rdoc
- 16. Ruby 樣式指南
- 17.模塊和混入
- 18.日期和時間
- 19.文件
- 20. Proc,Lambda 和塊
- 21.多線程
- 22.異常處理
- 23.正則表達式
- 24.寶石
- 25.元編程
- 26.基準
- 27.測試驅動開發
- 28.觀察者模式
- 29.模板模式
- 30.工廠模式
- 31.裝飾圖案
- 32.適配器模式
- 33.單例模式
- 34.復合模式
- 35.建造者模式
- 36.策略模式
- 贊助商
- 捐
- 人們怎么說
- 版權
- 取得這本書