# 0322 OOP、變數、方法、存取控制 ###### tags: `Ruby / Rails` `OOP` `物件導向設計` ## 物件導向程式設計 (Object Oriented Programming, OOP) - 使用人類可以理解的方式來組織管理程式碼:多一個類別的概念協助理解 - 分類:每種物件都有屬性跟方法、行為 --- ### 物件導向的兩大重點 1. 封裝:部分不想公開的介面 private,僅使用 public 的部分 2. 繼承:共同特徵放在同個分類 class 中(一層層分類繼承下去) ```ruby= class Animal # 類別要用常數(首字大寫)定義,寫 animal 不會動 end class Cat < Animal def eat puts "yummy" end end kitty = Cat.new #類別實體化 instance kitty.eat =>"yummy" #呼叫實體方法 # .new 方法(定義的時候是函數、使用的時候是方法) ``` - Ruby 內所有東西都是物件 - Ruby 內的類別需為常數(首字母大寫) | 類別分類 | 描述 | 舉例 | | -------- | -------- | -------- | | 具象類別 | 明確 | Cat | | 抽象類別 | 不夠明確的描述 | Animal | ## 類別 Class - 類別=模具 - 實體=做出來的成果(變數) - 要先做出來才能操作(實體->操作方法) --- ## 初始化 initialize - 固定寫法、不能拼錯 ```ruby= class Cat < Animal def initialize end end mimi = Cat.new # 類別實體化,OK kitty = Cat.new("kk") # error,給多餘的參數(excited 0, given 1) ``` - 初始化方法,接在每次類別實體化 (.new) 後面去做 - 預設不接參數、不做任何值 - 如果有寫,就會執行該方法(可設定參數) - 建立實體時會找最近有 initalize 的方法使用 - initalize 放在父層 =>所有的子層的 class 都會強迫需要帶入設定(不一定好) - overwrite 覆寫也發生在繼承上(子層有與上層相同定義的東西,以子層為主) - Animal def eat, Cat def eat => cat eat - 初始化通常沒有意義,通常搭配實體變數 ```ruby= class Cat < Animal def initialize(name) puts "ht #{name}" end end kitty = Cat.new("kk") ``` - [參考文章](https://ithelp.ithome.com.tw/articles/10160216) ```ruby= c = Cat.new d = Dog.new p c.is_a?(Cat) #是貓類嗎?true p d.is_a?(Cat) #是貓類嗎?false p d.is_a?(Animal) #是動物類嗎?true ``` --- ## 實體變數 @ ```ruby= #實體變數 class Cat def initialize(name) @name = name #使用同樣名稱是慣例,幫助理解從哪裡來,但不需要一樣名字 #@name <-實體變數 end end kitty = Cat.new("kk") kitty.eat #實體方法(作用在實體上) #執行實體方法前要先有實體 ``` - 執行實體方法前要先有實體(先Cat.new再執行)要有實體才能操作方法 ## 區域變數 ```ruby= class Cat def initialize(name) @name = name #@name <-實體變數 end def say_myname puts name #區域變數,找不到name參數 puts @name end end #所有實體變數存在各別實體內,跟著實體 ``` class也是一種物件 作用在類別身上的方法稱之為 類別方法 在方法前面寫上self ```ruby= class Cat def self.fly end end Cat.fly ``` --- 實體方法及類別方法有不同使用情境 ```ruby= class Book @book_name = "書名" @author = "作者" @book_array = {} def self.all # 印出所有書籍 end def self.find(number) # 找到指定編號的書籍 end def print_info puts @book_name puts @author end end Book.all # 此為類別方法 book1 = Book.find(1) # 此為類別方法 puts book1.print_info # 此為實體方法 ``` ##### 實體方法 vs 類別方法 ```ruby= #實體方法 m = Math.new puts m.abs(-1) puts m.sin(2) #類別方法:獨立類別叫出 puts Math.abs(-1) #不需要例外new出一個實體來做,比較直覺 puts Math.sin(2) ``` ![](https://i.imgur.com/e5hhRPf.png) ## 類別變數 @@ - 可以當作計數器使用 - 底下定義的類別變數會影響上層 - 類別變數不常用到,在rails中最常使用的是.all - 擁有者是類別,不是實體 - ==所有類別共用,盡量不要使用== - 沒有預設值 ```ruby= class Cat @@count = 0 #類別變數,作用範圍在類別 def initialize @@count += 1 end def self.ccc return @@count end end Cat.new #每次new就紀錄在類別內,計算他一共生出幾隻貓 Cat.new puts Cat.ccc ==>2 #new兩隻貓 ``` --- - Ruby沒有.屬性,所有的.name都是方法 - 要使用實體變數,需要先有一個方法 ### attr_accessor ```ruby= class Cat #attr_accessor :name = reader + writer #Getter=拿東西的人 => attr_reader :name (name為參數) def name @name end #Setter=設定東西的人 => att_writer :name att_writer :name def name=(n) @name = n end attr_accessor :age end kitty =Cat.new("abc") puts kitty.name() puts kutty.name = "ccc" #若無定義“name=“則錯誤,因為”=“也被歸類在變數命名中,所以要定義為”name=“方法,為以下: kitty.name= ("cccc") (上列的原型) p kitty.name ======> ccc lucky = Cat.new lucky.age #nil lucky.age = 18 # 18 ``` ### 手刻 Rails 類別方法:attr_accessor ```ruby= class Animal def self.my_attr_reader(method_name) define_method method_name do return instance_variable_get("@#{method_name}") end end def self.my_attr_writer(method_name) define_method "#{method_name}=" do |x| instance_variable_set("@#{method_name}", x) end end def self.my_attr_accessor(method_name) my_attr_reader(method_name) my_attr_writer(method_name) end end class Cat < Animal my_attr_accessor :name def initialize(name) @name = name end end kitty = Cat.new("abc") puts kitty.name kitty.name = "ccc" puts kitty.name ``` --- ### 類別重複定義 - 類別不會覆蓋,會融合在一起 - 所以Ruby常數可以修改:為了覆蓋功能(開放類別的存在) - 每個類別都是常數,開放類別之所以成立就是因為常數可修改 ```ruby= class Cat def eat end end class Cat def fly end end 以上等同於: class Cat def eat end def fly end end ``` - money patching /open class:去擴增內建的功能 - Rails就是一個巨型的猴子補丁 ```ruby= #修改(擴增)string字串的功能 class String def abc? true end end puts "12324".abc? ``` - self通常指相對定位 ```ruby= class Integer def days self #指物件本身,以下為例就是“5” end def ago "#{self} days ago" end end puts 5.days.ago #==>5.ago ==> "#{5} days ago" #照著順序帶入 #==>5 days ago ``` --- ## 存取控制 ### Private - 常應用在商用軟體上 - 需要功能但不想讓你看到內容 - 設計上不想被人拿取使用 - 範圍為寫了private以下到結束 - ==預設為public== ===> 封裝概念 ```ruby= class Cat def hello puts "你好" end def b end private def gossip puts "才不告訴膩勒" end end miffy = Class.new kitty.hello kitty.gossip ``` ```ruby= kitty.say_hello #kitty = receiver #say_hello = massage ``` ### private - 呼叫時不可以有小數點 - 不能有不明確的接收者receiver - 在類別可使用(不需要小數點) - 外部呼叫類別的方法(內含private) - ### 在public內呼叫private ```ruby= class Cat def hello gossip #放privat method end private def gossip end end kitty = Cat.new kitty.hello #利用publice call private ``` - ### 另一種存取private方式 - 呼叫send方法,再用send把message給gossip ```ruby= kitty.gossip #error #上等同以下 kitty.send(:gossip) # pass *所有物件都有send方法(內建) kitty.send(:send, :gossip) # pass 2層 ``` ![](https://i.imgur.com/tXAR8Jr.png) ### public ### protected - 不限定receiver - 幾乎用不到