昨天提到的實體方法,是為了創建的物件提供方法使用,而與實體方法相對的另一個方法叫類別方法,是類別本身的方法。
與實體變數不一樣,類別方法多用在提供特定的方法解決問題。先忽略class << self
,可以看到以下的三種方法appreciation_period
, notification_sent_at
, sf_sent_at
都是用來提供解決特定用途的方式。
class Utility
class << self
#==== 鑑賞期
#==== 用法: Utility.appreciation_period
def appreciation_period
Rails.env.production? ? 2.days : 1.minute
end
#==== 推播發送時間
#==== 用法: Utility.notification_sent_at
def notification_sent_at
send_time(8, 22)
end
#==== 物流收貨時間
#==== 用法: Utility.sf_sent_at
def sf_sent_at
send_time(11, 17).strftime("%F %T")
end
def send_time(start_hour, end_hour)
zero_am = DateTime.now.beginning_of_day
started_at = DateTime.now.beginning_of_day.change(hour: start_hour)
ended_at = DateTime.now.beginning_of_day.change(hour: end_hour)
if Time.current.between?(zero_am, started_at)
[false, started_at]
elsif Time.current.between?(ended_at, DateTime.now.tomorrow.beginning_of_day)
[false, DateTime.now.tomorrow.beginning_of_day.change(hour: start_hour)]
else
[true, Time.current]
end
end
end
end
接著我們看下列的ApiClient
為打API
用途所設計的類別方法。這種寫法在許多專案內很常看到,而將打API流程包覆成新方法的好處為
RestClient::Request
,若我們要改成其他方式如Net::HTTP
, Faraday
也是輕而易舉。使用上面的方法,我們不要改掉專案所有打Api
的地方,只需改ApiClient
即可。log
,以及針對不同的資料回應錯誤做rescue
下列程式碼還有許多看點
OpenStruct
的方法,我們這邊進行實際應用define_method
為一種動態宣告方法的一種方式,我們會在Day17介紹class ApiClient
DEFAULT_TIMEOUT = 200
# 範例
#
# GET => ApiClient.get 'https://jsonplaceholder.typicode.com/todos/1'
# POST => ApiClient.post 'https://jsonplaceholder.typicode.com/posts',
# payload: { title: 'foo', body: 'bar', userId: 1 }
class << self
%i(get delete post put patch).each do |http_method|
define_method(http_method) do |url, headers: { content_type: :json }, payload: {}, options: {}|
begin
Rails.logger.info("Request payload: #{payload},
request header: #{headers}, and url: #{url}")
response = RestClient::Request.execute(method: http_method, url: url,
payload: payload, headers: headers,
timeout: (options[:timeout] || DEFAULT_TIMEOUT))
Rails.logger.info("[Expected] Response code: #{response.code},
and response payload: #{response.body}")
OpenStruct.new({ code: response.code, body: response.body })
rescue RestClient::Exceptions::Timeout => e
Rails.logger.info("超過等待時間: #{e}")
OpenStruct.new({ code: nil, body: nil })
rescue RestClient::ExceptionWithResponse => e
Rails.logger.info("[Unexpected] Response code: #{e.response.code},
and response payload: #{e.response.body}")
OpenStruct.new({ code: e.response.code, body: e.response.body })
end
end
end
end
end
看完上述實際的例子後,接著我們看下列的基本例子
以下為類別方式的用法,簡單明瞭!
class Orangutan
def self.bar
'bar'
end
end
Orangutan.bar #=> bar
接著我們介紹在 Day11提及過的 <<
方法
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
end
orangutan = Animal.new
class << orangutan
def foo
'foo'
end
end
orangutan.foo
我們再宣告另外一隻紅色猩猩,並同樣的呼叫foo
,發現這隻紅色猩猩不會使用foo
方法
red_orangutan = Animal.new
red_orangutan.species
red_orangutan.foo #=> NoMethodError (undefined method `foo' for #<Animal:0x00007fd065edf480>)
以下的故事內容,如果有看關於我轉成為史萊姆的那檔事可能會比較好理解:
上述的例子,我們只為 orangutan 給予 foo 方法,這種給予方法的方式稱為單體方法 singleton
。單體的意思是,只有這個 orangutan 獨特技能 foo
,其他猩猩除非被授與,不然都不會得到獨特技能。
我們可以使用singleton_methods
查看 orangutan 的所有單體方法
orangutan.singleton_methods #=> [:foo]
再看以下例子,把foo
方法授與給Example 這個 class
,即為 ClassMethod
。
MetaClass
的概念為 a class which defines classes
ClassMethod
和singleton method
都有 metaclass
的概念,原因在於Example
本質也是物件,在 orangutan 物件上,給予單體方法,只有這個黑猩猩才有這個獨特技能,而ClassMethod
本質上,更像是為所有的猩猩族加護,讓所有的猩猩族都獲得了bar
技能~
class Orangutan
end
def Orangutan.bar
'bar'
end
Orangutan.bar #=> bar
ClassMethod
,和Javascript
的Prototype
的概念很像。Javascript
並沒有class
的概念,取而代之的是原型鍊prototype
。我們在MDN文件中常看到的一些方法,以reduce
舉例好了,文章的開頭標題會以如下的方式呈現。
Array.prototype.reduce()
它的意思即為,只要你用了Array
,就可以使用reduce
這個方法。
由於這裡的主角為Ruby,所以Javascript
的二三事,這裡就不便提太多,只是想說Array
透過Prototype
多了一個新方法,就像是Class 透過 MetaClass
的方法多了ClassMethod
。
<<
只是一個看起來比較厲害的語法糖。不過當大家都在用的時候,也就沒那麼厲害了?
class << Orangutan
def bar
'bar'
end
end
寫法1
的寫法等同於寫法2
。
#=== 寫法1
class Orangutan
def self.bar
'bar'
end
end
#=== 寫法2
class Orangutan
class << self
def bar
'bar'
end
end
end
明天我們會開始介紹 module
& mixin