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

第三課 物件導向基礎

公告

  • 簽收收據
  • 請提醒我要錄影

HTML & CSS

心理建設

該先學 Rails 還是 Ruby?

若想精通,靠上課的三十個小時是不夠的

推薦的書:

http://duck-on-rails.herokuapp.com/posts/eloquent-ruby

心理建設

Hash (雜湊)

# 另一種資料結構,可以把他想像成置物箱,每一個值(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"

Hash (雜湊)

# ‘=>' 符號叫做 hashrocket,是 hash 用來 assign 值所使用的符號
# 但是 Matz 老大認為 hashrocket 讓 hash 看起來太醜了...
# 所以出現了一種新的 hash 宣告方式 

person = { 
           name: "Bob", 
           age: 30,
           occupation: "Engineer"
         }

# 目前 Ruby 是兩種寫法都支援

Unless

# 文法上可以想成 "除非"

var = true

# 寫在符合條件要執行的程式碼後方

puts "It's true" unless var == false

# 除非 var 的值為 false,不然就會印出 "It's true"

Conditional Assignment

favorite_book = nil
puts favorite_book

#如果 favorite book 還沒有被設值,那就把它設定成左邊的值
favorite_book ||= "麥田捕手"
puts favorite_book

# => 麥田捕手

# 若變數已經有值,就不會設定新的值給它
favorite_book ||= "1984"
puts favorite_book

# => 麥田捕手

Implicit Return

# 在 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

警告!

Object Oriented 物件導向

 

  • 一種程式設計的方式
  • 利用資料與方法將現實生活的人事物 ”塑模“ 成許多數位化的個體
  • 我們就稱這些個體為物件 (Object)
  • 而一個系統就是由許多彼此互動的物件組成的

 

 

物件導向的好處

 

  • 易理解性
  • 可修改性

 

 

Object (物件)

 

 

  • 物件是一個擁有狀態與行為的實體

  • Ruby 語言裡,幾乎任何東西都是物件
  • 屬性與狀態就用資料表示
  • 行為就用方法表示

Class (類別)

  • 可以想像成是物件的模具

  • 產生出物件以前,就需要先將模具造好
  • 由類別製造出來的物件都稱為 "實例" (instance)
  • 只有實例才具備我們在模具裡定義的屬性和行為

*就好像章魚燒要先被模具做出來,才具備 “圓形“ 等屬性與咬一口會 "爆漿" 的行為

Class (類別)

# 宣告一個叫 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

initialize (初始化)

class Person
  # 每一個 person 物件在創出時會被呼叫到
  # 正式名稱是 constructor (建構式)
  def initialize
    puts "This object was initialized!"
  end
end

bob = Person.new     
# => "This object was initialized!"

Instance Variable (實例變數)

class Person
  def initialize(name, age)
    # instance variable 前面要加 @
    # 綁定一個物件屬性的值
    @name = name
    @age = age
  end
end

若不理解,就想像一個模具(Class)若是倒入不同原料,就可產出形狀相同但口味不同的章魚燒

Instance Method (實例方法)

# 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)

Accessor Method

# 假設我只想印出 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)

Getter Method

# 為了讀取物件裡的值,必須要寫一個 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

Setter Method

# 假設我是要寫入或更改 @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

Accessor Method

# 當然每次為了讀寫一個實例變數就寫 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

Self

# 現在我們知道用 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

Self

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

# 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

Self

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 Method (類別方法)

# 之前沒有提到的是 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

Class Variable (類別變數)

# 如果說 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

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
  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 方法

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

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

Inheritance vs Module

1. 一個類別只能繼承一個父類別,但卻可以 mix 很多                       module

2. 如果今天你是需要 "是某個..." 的關係,就用 inheritance,       如果需要 "擁有某項行為" 就用 module
    Ex. 蝙蝠是
屬於一種哺乳動物,就讓 Bat 繼承 Mammal
          蝙蝠有飛行的能力,但並非所有哺乳動物都會飛,所以讓            Bat include 擁有 fly 這個 method 的 module

3. Module 不能使用 new,他只能被其他 class 使用  

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!

當你的程式有 bug 時...

Buuuuuuuuuuuuuuug!!!

Pry gem

Ruby 語言的除錯工具

$gem install pry

$gem list pry

$gem uninstall pry-nav

Pry gem

在一般的 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

Pry gem

若要在 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'

Homework

用物件導向重構剪刀石頭布

 

https://github.com/yuyueugene84/lesson4/blob/master/RPS_OOP.rb

 

deck

By Eugene Chang