# 類(Class)與模塊(Module)
### 類
我們在對象和方法那一節中,提過這個概念。 比如 「人類」。
在面向對象概念中,一個類代表一組對象共同特征的抽象集合。比如「人類」,代表了人的共同特征,可以直立行走、會說話、會思考等人類特征。
~~~
*注:最好你可以打開irb或者是pry跟著練習*
在命令行輸入 irb 或 pry,回車,你就進入到了一個互動的Ruby Shell界面里,你可以在里面輸入代碼,并且會馬上得到運行結果。
chef-shell命令,就是基于irb來做的。
~~~
**用Ruby代碼表示就是:**
~~~
class People
def walk
puts 'can'
end
def say
puts 'hello word'
end
def think
puts 'I got!'
end
end
~~~
Ruby中用class關鍵字來聲明一個類,注意類名People,是大寫字母開頭。沒錯,這個People,就是一個常量。
~~~
person = People.new
~~~
我們可以使用new方法來創建一個對象, 這里的person,就是我們創造的一個人。
~~~
person.walk
person.say
person.think
~~~
人類的行為, 這個person都可以做。
當你運行以上代碼之后,會發現,這些方法都返回nil,這是因為Ruby的方法,如果你沒有明確使用return,默認只返回方法內部最后一行的運行結果,上面的方法中,最后一行都是puts語句,puts語句會返回nil。
當然你可以使用return語句來給方法指定返回值。比如:
~~~
def walk
return 'can'
end
~~~
但是他的名字呢,性別呢,種族或國籍等其他屬性呢?這些個體的屬性,是不可能每個人都一樣的,那么我們該怎么設定呢?
~~~
class People
def initialize(name="", gender="")
@name = name
@gender = gender
end
end
~~~
我們打開類Peopole,使用initialize 方法,來給一個類添加屬性。就是當你使用new方法創建一個對象的時候,這個對象可以被賦予屬性。
上面的代碼里, @name和@gender,都是實例變量, 而參數name,gender都是本地變量,也就是局部變量,它們可以被賦予默認值,但是,他們只能在initialize這個方法的作用域范圍內有效,所以,叫本地變量。
這樣,我們就可以重新創建一個person,指定name和gender了。
~~~
person = People.new('alex', 'man')
#=> #<People:0x007fb961313ce8 @gender="man", @name="alex">
~~~
當然,你僅僅創建了@name和@gender這倆實例變量,這還不夠,你還不能給這倆實例變量賦值以及獲取它們的值。如下:
~~~
person.name
#=> NoMethodError: undefined method `name' for #<People:0x007fb961313ce8 @name="alex", @gender="man">
~~~
所以,你必須要實現一對set/get方法。
我們再一次打開類,添加下面代碼:
~~~
class People
def name
@name
end
def name=(name)
@name = name
end
def gender
@gender
end
def gender=(gender)
@gender = gender
end
end
~~~
然后我們馬上再次執行下面代碼:
~~~
person.name
#=> "alex"
~~~
返回了我們期望的結果。我們也可以給person改名:
~~~
person.name = 'lee'
person.walk
person.say
person.think
~~~
### 開放類
如果你跟著上面的代碼走下來,會發現, 我們兩次都是直接打開People這個類來修改,每次修改都沒有重新添加之前的代碼,而People這個類生成的對象的行為,卻是只增不減。 尤其是你從其他語言轉過來的話,比如Java,會感到奇怪。
沒錯,這正是Ruby的特性之一: Open Class,開放類。
Ruby的類是可以隨便打開的,非常自由。
自由所帶來的結果是,有可能會被濫用,比如,你可以打開Ruby內置的類來添加方法:
~~~
class String
def to_iii
self.to_i
end
end
"111".to_iii
#=> 111
~~~
這種方式,有可能會影響到String類內置的方法,因為你無法記住每一個內置的方法,假如你添加的方法和內置的方法重名的時候,就完了,Ruby是不會警告你的,這樣可能會引起非常嚴重的bug。
我們把這種方式叫做monkey patch。 意思就是這種方式,比較原始,就像猴子沒進化到人這么高級一樣。 不過Ruby2.1給出了一個方案,具體可以參考我的blog的相關文章:[「Ruby2.1 Refinements」告別Monkey Patches](http://tao.logdown.com/posts/171266-ruby21-refinements-farewell-to-monkey-patches),這里不再累述。
### Chef的monkey patchs
在Chef這個工具里,也使用了這種方式:
鏈接:[https://github.com/opscode/chef/blob/master/lib/chef.rb](https://github.com/opscode/chef/blob/master/lib/chef.rb)
可以看到:
~~~
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
~~~
先不用管require這個方法,這是Chef對于Ruby原生的類,添加了自己的方法。被添加的這個方法,應該是想要被所有的相關對象都可以響應。比如,我們打開「chef/monkey_patches/object」
~~~
class Object
unless new.respond_to?(:tap)
def tap
yield self
return self
end
end
end
~~~
可以看到, Chef這種添加方式還是比較安全的。它加了一層保護,「unless new.respond_to?(:tap)」, 只有Object的對象不能響應這個tap方法,它定義的這個tap方法才可被響應。
這樣起到一個很好的保護作用。
### 模塊
模塊,使用module關鍵字來定義的,你可以把它當做一組方法集合。比如有一群人,他們有共同的興趣,比如踢球、看球、打dota。 但是這些興趣并不是所有人類都有的,這個時候就需要模塊了。
~~~
module Interest
def kickball
puts "i like kicking ball"
end
def read
puts "i like reading books"
end
def dota
puts "i like playing dota"
end
end
~~~
然后我們就可以為某個人賦予這些興趣:
~~~
person = People.new('張三', 'man')
person.extend Interest
person.kickball #=> "i like kicking ball"
person.read #=> "i like reading books"
person.dota #=> "i like playing dota"
~~~
注意,這里person是一個對象,我們可以使用extend來把Interest模塊的方法來拓展給person,讓他也擁有這些興趣。這種方式,叫做Mixin。
模塊,也可以作為命名空間來使用:
~~~
class Chef
module Mixin
module Command
# ...
end
module Template
# ...
end
end
end
~~~
這樣,我們可以使用Chef::Mixin::Command和Chef::Mixin::Template來使用這兩模塊,這就起到一個命名空間的作用。
### 繼承
我們可以使用模塊來為類添加一組方法, 當然也可以使用繼承來添加一個子類。 那我們剛才的例子來說,有一組相同興趣的人群, 他們首先是人類,然后才是有這種興趣的人。
~~~
class Fan < People
def kickball
puts "i like kicking ball"
end
def read
puts "i like reading books"
end
def dota
puts "i like playing dota"
end
end
~~~
我們定義一個類Fan, 使用 < 關鍵字來讓這個類繼承自People,那么Fan就擁有了People的所有屬性和行為。
~~~
person = Fan.new('李四', 'man')
person.kickball #=> "i like kicking ball"
~~~
同樣,李四和前面的張三,擁有了相同的對象。
我們可以把繼承和模塊結合起來使用, 因為興趣分很多類型,有運動、閱讀、打游戲,而運動種類,閱讀的種類和游戲的種類也很多,結合起來使用,會讓我們的代碼更加靈活:
~~~
module SportInterest
def walk; 'walk' end
def run; 'run' end
def swimming; 'swimming' end
# ...
end
module ReadInterest
def read_book; 'read book' end
def watching_tv; 'watching tv' end
# ...
end
module GameInterest
def cs; 'play cs' end
def dota; 'play dota' end
def wow; 'play wow' end
# ...
end
class SportFan < People
include SportInterest
end
class ReadFan < People
include ReadInterest
end
class GameFan < People
include GameInterest
end
~~~
這樣,我們可以隨便為各個興趣模塊里面添加各種項目,也不會影響到相關的Fan類。
再加上命名空間的概念,重構下上面代碼,就是下面這樣:
~~~
module Interest
module Sport
def walk; 'walk' end
def run; 'run' end
def swimming; 'swimming' end
# ...
end
module Read
def read_book; 'read book' end
def watching_tv; 'watching tv' end
# ...
end
module Game
def cs; 'play cs' end
def dota; 'play dota' end
def wow; 'play wow' end
# ...
end
end
class SportFan < People
include Interest::Sport
end
class ReadFan < People
include Interest::Read
end
class GameFan < People
include Interest::Game
end
~~~
這樣,代碼是不是看著更清晰了。
- 序
- Chapter 1: 初識Chef
- 一些背景
- Chef vs Puppet
- Chapter 2: Chef應用
- Chef架構
- Chef能做什么
- Chef組件
- Chef環境安裝
- chef-server
- opscode-chef
- chef-solo
- Chef實戰
- 實戰前的必修理論
- 使用Chef
- Chapter 3: Ruby基礎
- 對象與方法
- 標識符
- 類與模塊
- 數據類型
- 真與假
- 控制語句
- 代碼塊
- Chapter 4: Chef源碼架構
- Rubygems與gem
- bundler
- Chef源碼組織
- Chapter 5: Rails基礎
- Rails是什么
- MVC架構
- Restful
- Rails組成與項目結構
- Chapter 6: Chef Server WebUI
- Chef Server Webui組織結構
- Chef Rest API
- 參考