第三課 物件導向基礎
# 另一種資料結構,可以把他想像成置物箱,每一個值(value)會對應到一個鑰匙(key)
person = {
:name => "Bob",
:age => 30,
:occupation => "Engineer"
}
# 在上面的範例,:name, :age, :occupation 是 key,"Bob", 30, "Engineer" 是 value
# 因為 symbol 是 immutable (不能被改變的),所以很適合用來當做 key
# 若我要取得某個 value,我需要它的 key
person[:name]
# => "BOb"
# ‘=>' 符號叫做 hashrocket,是 hash 用來 assign 值所使用的符號
# 但是 Matz 老大認為 hashrocket 讓 hash 看起來太醜了...
# 所以出現了一種新的 hash 宣告方式
person = {
name: "Bob",
age: 30,
occupation: "Engineer"
}
# 目前 Ruby 是兩種寫法都支援
# 文法上可以想成 "除非"
var = true
# 寫在符合條件要執行的程式碼後方
puts "It's true" unless var == false
# 除非 var 的值為 false,不然就會印出 "It's true"
favorite_book = nil
puts favorite_book
#如果 favorite book 還沒有被設值,那就把它設定成左邊的值
favorite_book ||= "麥田捕手"
puts favorite_book
# => 麥田捕手
# 若變數已經有值,就不會設定新的值給它
favorite_book ||= "1984"
puts favorite_book
# => 麥田捕手
# 在 ruby 的 method 裡,其實在大部份的情況下不需要 return 關鍵字
def greater_than_five(operation)
if operation >= 5
true
else
false
end
end
operation = 5
puts greater_than_five(operation)
#印出 true
def area_of_circle(radius)
radius_squared = radius ** 2
area = radius_squared * 3.14
# 重點是,ruby 會回傳一個方法裡面最後一行被計算的結果
end
puts area_of_circle(10)
# 印出 314.0
物件是一個擁有狀態與行為的實體
可以想像成是物件的模具
*就好像章魚燒要先被模具做出來,才具備 “圓形“ 等屬性與咬一口會 "爆漿" 的行為
# 宣告一個叫 Person 的 class
# 注意 class 名稱要用 CamelCase,也就是每一個字的第一字母要大寫
class Person
# 任何 class 一定要加建構式
def initialize(name) # 建構式
@name = name # 前面有加 @ 的是實例變數(instance variable)
# 變數用來模擬人的屬性
end
def greet_me(word) # 方法用來模擬人的行為
puts "#{word}, #{@name}"
end
end
# 用 new 創出兩個 person 的物件
eugene = Person.new("Eugene")
eva = Person.new("Eva")
# person 物件才能使用 greet_me() 方法
eugene.greet_me("Good Morning")
#印出 Good Morning, Eugene
eva.greet_me("Hello")
#印出 Hello, Eva
class Person
# 每一個 person 物件在創出時會被呼叫到
# 正式名稱是 constructor (建構式)
def initialize
puts "This object was initialized!"
end
end
bob = Person.new
# => "This object was initialized!"
class Person
def initialize(name, age)
# instance variable 前面要加 @
# 綁定一個物件屬性的值
@name = name
@age = age
end
end
若不理解,就想像一個模具(Class)若是倒入不同原料,就可產出形狀相同但口味不同的章魚燒
# instance method(實例方法) 是只有被創造出來的物件才可使用
class Person
def initialize(name, age)
# instance variable 等同於在這個 class 的區域變數
@name = name
@age = age
end
# 宣告 instance methods,就像宣告一般的 method,只不過在 class 裡面
def greet
# instance variable 只可在 class 裡面被使用
puts "Hello, my name is #{@name}"
end
end
bob = Person.new("bob", 17)
bob.greet
# => Hello, my name is bob
# 若沒有先創出 Person 的instance (實例),也就是先 new 一個 Person 的 object
# 使用 instance method 就會噴錯
Person.greet
# => instance_method.rb:22:in `<main>':
# undefined method `greet' for Person:Class (NoMethodError)
# 假設我只想印出 bob 的名字,我需要讀取 @name 的值
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{@name}"
end
end
bob = Person.new("bob", 17)
bob.name
# => accessor_method.rb:14:in `<main>':
#undefined method `name' for #<Person:0x007facfa838188 @name="bob", @age=17> (NoMethodError)
# 為了讀取物件裡的值,必須要寫一個 method 讓物件回傳 @name 的值
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{@name}"
end
# 定義 get_name
def get_name
@name
end
end
bob = Person.new("bob", 17)
puts bob.get_name
# => bob
# 假設我是要寫入或更改 @name 的值
# 不能直接使用 bob.name = "Bob"
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{@name}"
end
# 讀取資料方法,稱為 getter method
def get_name
@name
end
# 寫入/更改方法,稱為 setter method
def set_name=(name)
@name = name
end
end
bob = Person.new("bob", 17)
bob.set_name = "Bob"
puts bob.get_name
# => Bob
# 當然每次為了讀寫一個實例變數就寫 getter 和 setter method 很麻煩
# 所以聰明的 ruby 就有了 attr_accessor 方法
class Person
# 在 attr_accessor 關鍵字後方加上 instance method 的 symbol
# 自動幫使用者把 getter 和 setter method 寫好
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{name}"
end
end
bob = Person.new("bob", 17)
bob.name = "Bob"
bob.age = 30
puts bob.name
puts bob.age
# => Bob
# => 30
# 現在我們知道用 attr_accessor,Ruby 會自動把 getter
# 和 setter method 產生出來
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{name}"
end
# 但假設說我需要在 class 裡面其他的 instance method 裡
# 呼叫 getter 和 setter method
def change_info(n, a)
# 希望能像在使用 setter method 一樣, bob.name = "Bob"
name = n
age = a
end
end
bob = Person.new("bob", 17)
bob.change_info("Bob", 30)
puts bob.name
# => bob
puts bob.age
# => 17
# 但是 bob 的資料卻沒變,為什麼?
# ruby 以為我們在 change_info 裡的 name 和 age 是指變數而不是 getter methods
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{self.name}"
end
# 為了確保 ruby 知道我們是要使用 Person 的 getter 和 setter method
# 需要 self 這個關鍵字
def change_info(n, a)
self.name = n
self.age = a
end
def print_info(name, age)
change_info(name, age)
puts name
puts age
end
end
bob = Person.new("bob", 17)
bob.print_info("Bob", 30)
puts bob.name
# => Bob
puts bob.age
# => 30
# self 可用於:
# 1. 在 class 裡呼叫 setter / getter / instance methods
# 2. 宣告 class method 時
# 但是 self 本身是什麼?
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{name}"
end
# 用這個 method 把 self 印出來
def who_am_i
puts self
end
end
bob = Person.new("bob", 17)
bob.who_am_i
# <Person:0x007ff1ba087dd0> self 在 instance method 裡
# 是指呼叫 instance method 的 object
# 所以在 instance method 裡面 self.age = bob.age
class Person
attr_accessor :name, :age
#今天我要是在class裡,instance methods 外看 self
puts self
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "Hello, my name is #{name}"
end
end
# => Person
# 所以在 class 裡面,instance method 外面,self 是指 class 自己
# 在在 class 裡面,instance method 裡面,self 是指被創出的某 object
# 之前沒有提到的是 class 本身也可以有自己的屬性與行為
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
# 定義 class 自己的 method
def self.hello
puts "hello, i am Person Class"
end
end
# 呼叫方式
Person.hello
#=> hello, i am Person Class
# 如果說 instance variable 是綁定物件的資料,那 class variable
# 就是綁定 class 本身的資料
class Person
attr_accessor :name, :age
# 宣告時前面加兩個 @@
@@number_of_people = 0
def initialize(name, age)
@name = name
@age = age
# 每創一個新的 object,就會被加一次
@@number_of_people += 1
end
def self.hello
puts "hello, i am Person Class"
end
# 回傳 @@number_of_people
def self.info
@@number_of_people
end
end
puts Person.info
bob = Person.new("Bob", 17)
puts Person.info
# => 1
sam = Person.new("Sam", 32)
puts Person.info
# => 2
# 我們希望在建立新的 class 時,新的 class 也包含了舊的 class 的方法和特性
# 這時就可以使用 inheritance(繼承),讓我不用重新寫很多東西
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "hello, my name is #{name}"
end
end
# 使用 < 來代表繼承
class Engineer < Person
def program
puts "I know programming"
end
end
class Salesman < Person
def talk
puts "talk talk talk"
end
end
bob = Engineer.new("Bob", 17)
sam = Salesman.new("Sam", 32)
bob.greet
# => hello, my name is Bob
sam.greet
# => hello, my name is Sam
# Engineer 和 Salesman 繼承了 Person 的 greet 方法
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "hello, my name is #{name}"
end
end
# 如果我希望在改變 Engineer 的 greet
class Engineer < Person
# override (覆寫) 原本 Person 的 greet
def greet
puts "I am an Engineer, my name is #{name}"
end
end
# 創立一個叫 German 的子類別
class German < Person
# override (覆寫) 原本 Person 的 greet
def greet
puts "Hallo, mein name ist #{name}"
end
end
bob = Engineer.new("Bob", 17)
martin = German.new("Martin", 20)
bob.greet
martin.greet
#=> I am an Engineer, my name is Bob
#=> Hallo, mein name ist Martin
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def greet
puts "hello, my name is #{name}"
end
end
class Engineer < Person
attr_accessor :lang
def initialize(name, age, lang) # 若我今天想改寫 Engineer 的建構式,可以傳入三個參數
@lang = lang
super(name, age)
# super 會呼叫 parent class 裡面同樣名字的 method
# 注意 super 只能在 method 裡面使用
end
end
bob = Engineer.new("Bob", 17, "Ruby")
puts bob.name
puts bob.age
puts bob.lang
#=> Bob
#=> 17
#=> Ruby
# 可把 module 想像成工具箱,本身並不是 class
# 沒有實例、不可 new,只能被 include 到其他 class 裡面使用
# 一個 class 使用模組在 ruby 界是叫做 mixin module
module Knowledge
def program
puts "I know how to program"
end
end
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
class Engineer < Person
include Knowledge
end
bob = Engineer.new("Bob", 17)
bob.program
# => I know how to program
1. 一個類別只能繼承一個父類別,但卻可以 mix 很多 module
2. 如果今天你是需要 "是某個..." 的關係,就用 inheritance, 如果需要 "擁有某項行為" 就用 module
Ex. 蝙蝠是屬於一種哺乳動物,就讓 Bat 繼承 Mammal
蝙蝠有飛行的能力,但並非所有哺乳動物都會飛,所以讓 Bat include 擁有 fly 這個 method 的 module
3. Module 不能使用 new,他只能被其他 class 使用
# method 有三種不同的權限
# public 就是任何屬於這個 class 的物件都能使用
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
# greet 就是 public method
def greet
puts "hello, my name is #{name}"
end
end
bob = Person.new("Bob", 30)
bob.greet
#可以在程式的任何地方被使用
# private 是只有在 class 內部才可以存取
# 適用於有些時候,你希望一些 method 不會被程式的其他地方使用到(像是金流、身分證字號 etc.)
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def identity
#只能在 class 裡面被其他 instance method 呼叫,前面不能加 self
puts "secretly, #{secret_method}"
end
private # 先寫 private 關鍵字
def secret_method
"I am Iron Man!"
end
end
bob = Person.new("bob", 17)
bob.secret_method
#=> private method `secret_method' called
#=> for #<Person:0x007ff182033498 @name="bob", @age=17> (NoMethodError)
# private method 不可直接被物件呼叫
bob.identity
# secretly, I am Iron Man!
Buuuuuuuuuuuuuuug!!!
Ruby 語言的除錯工具
$gem install pry
$gem list pry
$gem uninstall pry-nav
在一般的 ruby 程式裡:
require 'pry'
# 在程式的最上方寫 require 'pry'
class Player
attr_accessor :hand
attr_reader :name
def initialize(name)
@name = name
@hand = nil
binding.pry
# binding.pry 等於在 ruby 的程式碼裡面下斷點,程式執行到此就會停住
end
end
若要在 rails 專案裡使用,就在 gemfile 加上:
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.6'
# 在 gemfile 裡面 寫上 gem 'pry',再執行 bundle install
gem 'pry'