iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
自我挑戰組

Ruby on Rails JS系列 第 11

Ruby on Rails 繼承(Inheritance)與開放類別

到目前為止的範例都是只有單一類別,但在真實的世界裡其實是更複雜的,像是如果想要再加入一個小狗類別:

class Cat
  def eat(food)
    puts "#{food} 好好吃!!"
  end
end

class Dog
  def eat(food)
    puts "#{food} 好好吃!!"
  end
end

在這個例子,不管是 Cat 或 Dog 類別都有定義了 eat 方法,在物件導向的概念裡,如果這些類別的用途是同一型的(例如 Cat 跟 Dog 都是動物),通常會把相同功能的方法移到「上一層」的類別裡,然後再去繼承它:

class Animal
  def eat(food)
    puts "#{food} 好好吃!!"
  end
end

class Cat < Animal
end

class Dog < Animal
end

在這裡,我定義了一個 Animal 類別,然後讓 Cat 跟 Dog 都去繼承它,那個小於符號 < 就是「繼承」的意思。這樣一來,就算 Cat 跟 Dog 類別空空的什麼都沒寫,也一樣有 eat 方法可以用。雖然 Cat 跟 Dog 本身是兩個不同的類別,但我們可以說「Cat 是一種 Animal,Dog 也是一種 Animal」,利用這樣的設計,可以把程式碼整理得更漂亮,不會寫出一堆重複的程式碼。

開放類別(Open Class)

大家請先看一下這段程式碼:

class Cat
  def abc
    # ...
  end
end

class Cat
  def xyz
    # ...
  end
end

kitty = Cat.new
kitty.abc       # => 會發生什麼事?
kitty.xyz       # => 會發生什麼事?

一個不小心,在這裡定義了兩個名字都叫做 Cat 的類別,你可能會猜,後面寫的類別應該會蓋掉前面先寫的類別,所以 kitty.xyz 可正常運作,但 kitty.abc 會出錯。

但在 Ruby 裡,如果遇到兩個一樣名字的類別,其實並不會「覆蓋」,而是會進行「融合」,上面這兩個類別最後會變成這樣:

class Cat
  def abc
    # ...
  end

  def xyz
    # ...
  end
end

然後 abc 跟 xyz 兩個方法都可以正常執行。利用這個特性,可以做出有趣的效果:

class String
  def say_hello
    "hi, I am #{self}"
  end
end

puts "eddie".say_hello  # => hi, I am eddie
puts "kitty".say_hello  # => hi, I am kitty

在這裡,我先定義一個 String 類別,並且在裡面定義了 say_hello 方法,根據前面的規則,遇到「同名」的類別的話,方法會互相「融合」,所以在那之後所有的字串就都有 say_hello 方法可以用了。等等,那個 String 類別不是內建的類別嗎?是的,你沒看錯,在 Ruby 即使是內建的類別,也是可以幫它「加料」的,這個技巧稱之開放類別(Open Class)。

這是我個人很喜歡的功能,有些人可能會認為這樣的設計感覺很隨便,竟然連內建的類別都可以任意修改,但我想大家都是大人了,應該不會沒事亂 open 然後去惡搞自己或自己的同事吧。事實上,Rails 本身也正是利用這個特性,讓程式碼的可讀性變得更好,例如:

puts 3.days.ago    # => Wed, 21 Dec 2016 12:06:13 UTC +00:00
puts 10.megabyte   # => 10485760
這樣不是很酷嗎 :)

一加一不等於二?
還記得在前面有提到,1 + 2 其實是「1 這個數字物件,呼叫了 + 方法,並且把 2 這個數字物件當做參數傳給它」,事實上,open class 不僅能幫原來的類別加功能,連原來即有的行為都可以改掉:

class Integer
  def +(n)
    1000
  end
end

puts 1 + 2   #=> 得到 1000
puts 3 + 4   #=> 得到 1000

我在 Integer 類別裡重新定義了 + 方法,讓它總是回傳數字 1000,這樣一來,不管是 1 + 2 或是 3 + 4,答案都會是 1000。

這樣其實沒什麼用,不過可以再做一點事情,讓原本的 + 還是能算出它原本的答案,但又還可以做些額外的事:

class Integer
  alias :original_plus :+

  def +(n)
    puts "hey hey hey"
    original_plus(n)
  end
end

puts 1 + 2
puts 3 + 4
在這裡我使用了 Ruby 內建的 alias 方法把原本的 + 方法加個別名 original_plus,然後再新定義的 + 方法裡,再呼叫它原本的算法。執行之後就會發現計算結果跟原本的 + 是一樣的,但會偷偷多印了 hey hey hey 字樣在畫面上。至於這個 hey hey hey 可以做什麼,就看大家的想像力了。

Ruby 是個設計上很特別也很彈性的程式語言,不僅很多東西都不是它看起來的樣子(例如 + 號竟然只是個方法),連內建的方法如果不喜歡都可以直接改掉(例如把 + 方法的結果改掉)。
[為你自己學Ruby on Rails]https://railsbook.tw/chapters/08-ruby-basic-4.html


上一篇
Ruby on Rails 方法的存取控制
下一篇
Ruby on Rails 模組(Module)
系列文
Ruby on Rails JS29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言