Ruby on Rails 網站程式設計基礎班

第3.5課:深入物件導向
Ruby 風格指南
何時一定會用到 return?
# Early return 的狀況
def add_two(arr)
# 確保每一個 hash 的 age 屬性都有值,若沒有,就迴傳錯誤訊息,不執行下面的程式碼
return "missing age value..." if arr.any? { |x| x[:age] == nil }
arr.map { |x| x[:age] = x[:age] + 2}
arr
end
hash1 = { :age => 19}
hash2 = { :age => 21}
# hash3 = { :age => 31}
hash3 = { :age => nil}
arr = [hash1,hash2,hash3]
puts add_two(arr)
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
def get_name
name # 若已使用 attr_accessor,可省略 self
end
def set_name1=(result)
name = result # Ruby中若偵測到name=,會建立一個新的區域變數name,而不是呼叫 name 方法
end
end
bob = Person.new("bob", 17)
puts bob.get_name
# => bob
bob.set_name1 = "Bob"
puts bob.get_name
# => bob 沒有改變
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
def get_name
name
end
def set_name2=(result)
# 使用 setter method 時,為了區別區域變數,這種情況下必須使用self明確指定
self.name = result
end
end
bob = Person.new("bob", 17)
puts bob.get_name
# => bob
bob.set_name2 = "Bob"
puts bob.get_name
# => Bob
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
def get_name
name
end
def set_name3=(name)
# 當然,我也可以直接設定實例變數
@name = name
end
end
bob = Person.new("bob", 17)
puts bob.get_name
# => bob
bob.set_name3 = "Bob"
puts bob.get_name
# => Bob
@ vs method
# 把 instance variable 包在方法裡有許多好處
# 假設今天我的帳務系統有一個應收帳務的 class
class AccountReceivable # 應收帳務
def initialize(title, amount)
@title = title
@amount = amount
end
def sales_tax_amount #計算營業稅金額
@amount * 0.05
end
def invoice_amount #計算加上稅的帳面金額
@amount * 1.05
end
end
@ vs method
# 若今天收到新的需求:以後系統的營業稅金額與帳面金額
# 必須轉成用美金計價(輸入依然是台幣)...
# 這時就需要修改多處地方...
class AccountReceivable # 應收帳務
def initialize(title, amount)
@title = title
@amount = amount
end
def sales_tax_amount #計算營業稅金額
#@amount * 0.05
@amount / 32 * 0.05
end
def invoice_amount #計算加上稅的帳面金額
#@amount * 1.05
@amount / 32 * 1.05
end
end
@ vs method
# 若我一開始就把 @amount 包在 accessor method 裡面...
class AccountReceivable # 應收帳務
attr_accessor :title, :amount
def initialize(title, amount)
@title = title
@amount = amount
end
# 把 amount 的邏輯集中到程式一處
# 之後若要修改,我只需要修改 amount 的 getter method 一處即可
def amount
@amonut / 32
end
def sales_tax_amount
amount * 0.05
end
def invoice_amount
amount * 1.05
end
end
@ vs method
class AccountReceivable # 應收帳務
attr_accessor :title, :amount
def initialize(title, amount)
@title = title
@amount = amount
end
def amount
@amount / 32
end
def sales_tax_amount #計算營業稅金額
amount * 0.05
end
def invoice_amount #計算加上稅的帳面金額
amount * 1.05
end
end
# 若是把 @amount 包在方法裡,就可以在 class 以外的地方被使用
class Rebate # 現金回饋
attr_accessor :account_receivable, :rate
def initialize(account_receivable, rate)
@account_receivable = account_receivable
@rate = rate
end
def calculate_rebate
# 使用應收帳款的 amount 方法計算現金回饋金額
account_receivable.amount * rate
end
end
receivable1 = AccountReceivable.new("應收帳款1", 10000)
puts receivable1.amount
#=> 312
# 計算回扣金額
rebate = Rebate.new(receivable1, 0.02)
puts rebate.calculate_rebate
#=> 6.24
# 計算實際收入的金額
puts receivable1.invoice_amount - rebate.calculate_rebate
#=> 321.36
@ vs method
結論:盡量把實例變數包在 method 裡面
1. 較高的可見度 (可在 class 外被使用)
2. 比較靈活的寫法 (更能適應改變)
用 hash 傳參數
# 若今天一個方法我需要傳進許多參數...
class Person
# 宣告方法的介面時,要寫的 code 就會變得又臭又長...
def initialize(name, age, hair_color, favorite_book, occupation)
@name = name
@age = age
@hair_color = hair_color
@favorite_book = favorite_book
@occupation = occupation
end
end
# 更麻煩的是,在使用上還必須依照順序傳入參數...
bob = Person.new("bob", 17, "black", "1984", "Salesman")
用 hash 傳參數
class Person
attr_accessor :name, :age, :occupation
# 可以把方法改成用 hash 傳遞參數
def initialize(options ={})
@name = options[:name] # 到hash內取值
@age = options[:age]
@hair_color = options[:hair_color]
@favorite_book = options[:favorite_book]
@occupation = options[:occupation]
end
end
hash = {
hair_color: "black",
name: "bob",
age: "1984",
occupation: "Salesman",
favorite_book: "1984"
}
# 使用時就丟 hash...
bob = Person.new(hash)
Constants (常數)
# constant 就是有些時候會遇上一些在程式裡不應改變的變數
# 像是圓周率、法定成年的年紀 etc.
class Person
# 宣告 constant,全部大寫,命名用尖叫蛇底式大寫 SCREAMING_SNAKE_CASE
LEGAL_AGE_TO_DRIVE = 18
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
# 看這個人是否已到可以考駕照的年齡
def can_drive?
if age >= LEGAL_AGE_TO_DRIVE
puts "yes #{name} can drive"
else
puts "no #{name} can't drive"
end
end
end
bob = Person.new("Bob", 17)
bob.can_drive?
#=> no Bob can't drive
Module (模組)
# 可把 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
Module (模組)
Ruby 使用 Module來解決多重繼承的問題。不同類別但是要擁有相同的方法,就可以改放在 Module 裡面,然後 include 它即可
Inheritance (繼承)
# 我們希望在建立新的 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
end
class Salesman < Person
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 方法
Inheritance vs Module
1. You can only subclass from one class. But you can mix in as many modules as you'd like.
你只能為一個 class 建立子類別,但卻可以 mix 很多 module
2. 如果今天你是需要 "是某個..." 的關係,就用 inheritance,如過需要 "擁有某項行為" 就用 module
Ex. 蝙蝠是屬於一種哺乳動物,就讓 Bat 繼承 Mammal
蝙蝠有飛行的能力,但並非所有哺乳動物都會飛,所以讓 Bat include 擁有 fly 這個 method 的 module
3. Module 不能使用 new,他只能被其他 class 使用
Method Override (覆寫)
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
Super
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
Method Lookup
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
# 尋找 Engineer 的祖先們
puts Engineer.ancestors
# Engineer
# Knowledge
# Person
# Object
# Kernel
# BasicObject
# 一旦 object 呼叫了一個它的 Class 裡面沒有的 method
# 他就會依序找他所有的祖先,若找到了有這個 method 的祖先,就使用這個祖先類別的 method
Public Method
# 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 Method
# 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!
Private Method
# 今天我們的帳務系統有新需求:若是尊榮級的合作對象,就加 1% 的現金回饋
class Rebate # 現金回饋
attr_accessor :account_receivable, :rate, :company
def initialize(account_receivable, rate, company)
@account_receivable = account_receivable
@rate = rate
@company = company
end
def rebate_amount
calculate_rebate
end
private
def calculate_rebate
# 但是我們知道現金回饋的算法會經常改變
# 寫成 private method,再由一個 public method 呼叫,會是比較
# 能夠適應改變的寫法
if company.partnership == "premium"
account_receivable.amount * (rate + 0.01)
else
account_receivable.amount * rate
end
end
end
# 代表公司(合作廠商)的 class
class Company
attr_accessor :name, :partnership
def initialize(name, partnership)
@name = name
@partnership = partnership
end
end
Private Method
結論:盡量把預期會經常改變的程式碼寫成 private methods,不常改變 / 穩定的方法寫成 public methods,避免修改 method 的時候,所有依靠該 method 的程式碼也要一起修改
1.
OOP Design
*越容易維護或擴充功能的程式碼(越能適應改變的程式碼),就是越高明的設計
Ruby 程式碼風格
雖然你已經看了 Ruby 風格指南
但還是經常發生不知道自己寫的 Code 風格是否正確
Rubocop Gem
Ruby 的程式碼風格分析器

Rubocop Gem

Rubocop Gem
$ gem install rubocop
接下來就可以在終端機裡直接輸入 Rubocop 即可:
$ rubocop 你的ruby程式碼檔案路徑
安裝 Rubocop gem
SQLite Browser
Relational Database (關聯式資料庫)
關聯式資料庫教學:
Relational Database (關聯式資料庫)
Homework
Extra
HTML & CSS 練習
https://www.codecademy.com/learn/web
Ruby 語言練習
https://www.codecademy.com/learn/ruby
SQL 語言練習
https://www.codecademy.com/learn/learn-sql
http://sqlbolt.com/
Ruby on Rails 網站程式設計基礎班
By Eugene Chang
Ruby on Rails 網站程式設計基礎班
- 1,503