繼承
Ruby也跟其他物件導向一樣,有著繼承(inheritance)的概念,可以讓類別從另一個類別繼承方法。
繼承可把常用的方法移往單一類別,而不必在多個類似的類別中重複定義方法,可以讓其他類別繼承該類別。具有常用方法的類別稱為超類別(superclass)或稱為父類別,繼承這些方法的類別稱為子類別(subclass)。
這邊有Car,Truck,Motocycle等類別具有若干共同的實體方法和屬性,每個類別用於代表一種類型的種類。這邊建立一個新的類別,將該類別取名為Vehicle,並把用的方法和屬性移往該處。這樣Vehicle類別稱為三個類別的父類別。而Car,Truck,Motocycle則稱為Vehicle的子類別。
在Ruby中,子類別不會繼承實體變數;他們所繼承的是用於建立這些變數的屬性存取器方法(attribute accessor method)。
定義父類別
為了消除Car,Truck.Motorcycle等類別中重複的方法,把共享的方法和屬性移往Vehicle類別。Car,Truck.Motorcycle皆為Vehicle的子類別,繼承了Vehicle的方法。
class Vehicle
attr_accessor :odometer
attr_accessor :gas_used
def accelerate
puts "Floor"
end
def sound_horn
puts "Beep"
end
def strre
puts "Turn"
end
def mileage
return @odometer / @gas_used
end
end
定義子類別
子類別定義就像一般類別的定義。
Ruby使用小於(<)符號是因為子類別是父類別的一個子集合。
class Car < Vehicle
end
class Truck < Vehicle
end
class Motorcycle < Vehicle
end
只要把Car,Truck,Motorcycle定義成子類別,他們就會繼承Vehicle的所有屬性和實體方法。即使子類別本身不包含任何程式碼,所建立的任何實體將可獲得父類別的所有功能。
truck = Truck.new
truck.accelerate
truck.steer
car = Car.new
car.odometer = 11432
car.gas_used = 366
puts "HIII"
puts car.mileage
現在Car,Truck,Motorcycle類別皆具有相同的功能,但沒有重複的程式碼。
添加方法到子類別
現在要添加新的方法,但避免影響到他類別,所以不能更改到父類別,所以要把新的屬性定義在要加的子類別上。
class Truck < Vehicle
attr_accessor :cargo
def load_bed(contents)
puts "Securing #{contents} in the truck"
@cargo = contents
end
end
我們可以建立新的實體,然後承載和存取。
truck = Truck.new
truck.load_bed("259")
puts "The truck is carrying #{truck.cargo}"
實體變數屬於物件,非類別
定義於父類別的實體變數會由子類別所繼承
Car會從Vehicle繼承@odometer和@gas_used等實體變數。所有的Ruby皆具有名為instance_variables的方法,對物件呼叫該方法,能看到物件具有哪些實體變數。
car = Car.new
puts "car.instance_variables"
這樣輸出會沒有輸出,是因為car尚未具有任何實體變數,等到物件的實體方法,才會在物件上建立實體變數。這邊呼叫odometer和gas_used等屬性寫入器方法:
car.odometer = 22914
car.gas_used = 728
puts car.instance_variables
所以Car類別不會繼承@odometer和@gas_used等實體變數…而是繼承ometer= 和gas_used= 等實體方法,並使用這些方法來建立實體變數。
假設有一個不符合規定的父類別,他使用@storage實體變數來為他的name=和name存取器方法保存值,有一個子類別使用相同的變數名稱,@storage來為他的salary=和salary 存取器方法保存值。
class Person
def name = (new_value)
@storage = new_value
end
def name
@storage
end
end
class Employee < Person
def salary = (new_value)
@storage = new_value
end
def salary
@storage
end
end
當我們實際使用Employee子類別的時候,只要我們對salary屬性賦值,就會覆寫name屬性,因為他們使用了相同的實體變數。
employee = Employee.new
employee.name = "Easy fun"
employee.salary = 89000
puts employee.name
確保總是使用與你的屬性存取器名稱一致的合理變數名稱。
覆寫方法
如果父類別的行為並非子類別需要的,繼承功能為你提供了另一個有用的機制:方法覆寫(override)。這個機制讓你得以使用子類別專用的方法來取代從父類別繼承來的方法。
class Motorcycle < Vehicle
def steer
puts "Turn right"
end
end
我們呼叫一個Motorcycle實體的steer方法,所用到的是經過覆寫的方法,我們所用到的是Motorcycle類別中所定義的steer方法,而非從Vehicle繼承來的方法。
但我們呼叫一個Motocycle實體的任何其他方法,所用到的是繼承來的方法。
motorcycle.accelerate
如果Ruby看到呼叫端所要求的方法定義於子類別,他將會呼叫該方法並停在該處。
但如果沒有在子類別找到該方法,Ruby將會到父類別中尋找該方法,然後再到父類別的父類別中…以此類推,沿著繼承鏈往上尋找。
如果需要改程式,可以在Vehicle類別中進行,所做的改變會自動傳播到子類別;就是每個子類別都會得到更新的好處。如果一個子類別需要特殊的行為,直接覆寫從父類別繼承來的方法就可。
super關鍵字
使用super關鍵字時會導致父類別中同名被呼叫。
class Person
def greeting
puts "Hello"
end
end
class Friend < Person
def greeting
super
puts "Nice to meet u"
end
Friend.new.greeting
end
我們呼叫子類別上這個進行複寫的方法,將看到super關鍵字如同在呼叫父類別上遭覆寫的方法。
super關鍵字就是一個普通的方法呼叫。
class Person
def greeting
puts "Hello"
end
end
class Friend < Person
def greeting
basic_greeting = super
"#{basic_greeting} Nice to meet u"
end
end
puts Friend.new.greeting
super也可以傳引數給他,而這些引數將會傳給父類別的方法。
class Person
def greeting_by_name(name)
"Hello, #{name}!"
end
end
class Friend < Person
def greeting_by_name(name)
basic_greeting = super(name)
"#{basic_greeting} Nice to meet u"
end
end
puts Friend.new.greeting_by_name("Easy")
super不同於一般的方法呼叫:如果未傳遞引述給他,Ruby會自動把你傳遞給子類別方法的引數拿來呼叫父類別方法。
class Person
def greeting_by_name(name)
"Hello, #{name}!"
end
end
class Friend < Person
def greeting_by_name(name)
basic_greeting = super
"#{basic_greeting} Nice to meet u"
end
end
puts Friend.new.greeting_by_name("Easy")
super與super()不一樣,super以進行覆寫的方法。super()是在呼叫遭覆寫的方法時未傳入引數,即使覆寫的方法有收到引數。
使用super的子類別
可以把方法中重複的程式碼代換成呼叫super,以便利用父類別之方法所提供的功能。
class Armadillo < Animal
def move(destination)
puts "#{@name} unrolls"
super(destination)
end
end
#或是
class Armadillo < Animal
def move(destination)
puts "#{@name} unrolls"
super
end
#自動傳遞,當被呼叫傳入的引數
end
dillon = Armadillo.new
dillon.name = "Easy"
dillon.move("burrow")
Object類別
Ruby物件皆會繼承Object類別
如果沒有為所定義的類別指定父類別,Ruby會自動替該類別設置名為Object的父類別。
即使有為你的類別指定父類別,該父類別也可能繼承Object。幾乎每一個Ruby物件不是直接就是間接,以Object為父類別。
Ruby這樣是因為Object類別為幾乎所有的Ruby物件定義了所需要的許多有用方法:
🌏 to_s:把物件轉換成字串。
🌏 inspect: 把物件轉換成除錯字串。
🌏 class: 告訴你一個物件是哪一個類別的實體。
🌏 methods:告訴你一個物件具有哪些實體方法。
🌏 instance_variables:列出一個物件的實體變數清單。
Ruby物件從Object類別繼承了許多做為基礎工具方法。
覆寫繼承來的方法
把Dog類別的父類別指定為Animal類別。因為沒有為Animal指定為父類別,Ruby會把Object類別指定為他的父類別。
要是Aniaml實體會從Object類別繼承to_s方法。Dog實體會從Animal繼承to_s。當把Dog物件傳遞給puts或print,他的to_s方法會被呼叫,以便把他轉換成一個字串。
class Dog < Animal
def to_s
puts "#{@name} the dog, age #{@age}"
end
end
mike = Dog.new
mike.name = "Easy"
mike.age = 4
phil = Dog.new
phil.name = "Rex"
phil.age = 2
puts mike.to_s, phil.to_s
甚至可以改成
puts mike, phil