跳到主要內容

Ruby: module, include, extend, self, module_function

module

一個最簡單的module

include

在class使用include, 可以讓module內的方法為class的物件方法,在ruby稱為mixin

include可用在模擬多重繼承,因Ruby不像C++可以直接多重繼承,所以宣告為module,在mixin到class裡面

extend

在class使用extend,可以讓module內的方法為class的類別方法

與include的不同點在於

  • include: module內的方法為class的物件方法
  • extend: module內的方法為class的類別方法
module Foo
  def foo
    puts 'foo'
  end
end

class Bar
  extend Foo
end

Bar.foo  # 印出hello foo (foo已成為Bar的類別法)

self in module

self是什麼?就是呼叫Ruby當前物件本身指標所指的,在主程序直接呼叫self返回main。

先看最簡單的module

module Foo
end

直接呼叫self會出現什麼呢?

module Foo
  p self #印出 Foo
end

別忘了ruby式腳本式的語言,這個程式沒寫錯,單純只是宣告module,他裡頭的命令句還是被執行,所以印出Foo

那麼這個Foo物件是什麼?簡單,印出來看看

module Foo
  p self.class #印出Module
end

非常清楚,Ruby號稱所有東西都是物件,所以module也是物件,所以self的使用很清楚了,直接動態的在Foo物件上面加上方法

module Foo
  def self.foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo, 直接呼叫Foo這個Module形態的物件,裡頭的foo方法

所以同理,class裡面的self也是一樣的應用,class也是可以實體化為物件,並動態的加上方法,通常稱為類別方法,這種類別方法,在其他程式語言裡面也很常用,只是實作方式不同,Ruby是把class也當作物件處理

extend self

綜合上面所說的,如果module裡面 extend self會發生什麼事呢?

module Foo
  extend self

  def foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo

如果在class內使用extend會使module內的方法,成為類別方法,那如果在module使用,同樣會成為module方法,這個講法並不好,因為在Ruby裡面都是物件,只是在其他語言稱為類別方法,所以我照著命名

extend self如果有點難懂,先看extend 別的模組

module Foo
  def foo
    puts 'hello foo'
  end
end

module Bar
  extend Foo
end

Bar.foo # 印出hello foo (foo成為bar的類別方法)

就在跟class裡面extend一樣。同理extend self,就可以讓自己本身的方法,可以用module方法的方式呼叫,也可以被class include時,被當成物件方法呼叫

module_function

從名稱得知,使用module_function,則只能當成module function來使用

將要變成module function的方法名稱,使用symbol的方式傳入

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

Foo.foo  印出hello foo(變成module方法)

跟上面extend self不同的是,它就只能當module方法,如果在class內include,也不會變成物件方法

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

class Brb
  include Foo
end

Foo.foo # 印出hello foo
brb = Brb.new
brb.foo # 跳出NoMethodError錯誤,因為foo只能當module方法,不是當物件方法來呼叫

included

還有一個動態的方法,讓class include時,再決定哪些是類別方法,哪些是物件方法

module內建一個方法叫做include,可以在該module被include時被呼叫,舉個例子

module Foo
  def self.included(receiver)
    puts "#{self}被#{receiver} include進去了"
  end
end

class Brb
  include Foo #印出Foo被Brb include進去了
end

所以我們可以在module內決定include它的class要哪些類別方法,幫它(class)呼叫extend就行了,例如

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.extend Bar # 幫它(class)呼叫extend 並extend Bar進去
  end
end

class Brb
    include Foo
end

Brb.bar # 印出hello bar

同理,也可以幫它呼叫include

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.send :include, Bar # 幫它(class)呼叫include 並include Bar進去
  end
end

class Brb
    include Foo
end

brb = Brb.new
brb.bar # 印出hello bar

也可以在module直接嵌套在module裡面 讓程式馬更簡潔,下面示範嵌套完,並直接呼叫extend與include

module Foo
  module Bar # 將用來當類別方法
      def bar
        puts 'hello bar'
    end
  end

  module Bla # 將用來當物件方法
    def bla
        puts 'hello bla'
    end
  end

  def self.included(receiver)
    receiver.extend Bar # 將Bar設定為類別方法
    receiver.send :include, Bla # 將bla設定為物件方法
  end
end

class Brb
    include Foo
end

Brb.bar
brb = Brb.new
brb.bla

留言

這個網誌中的熱門文章

HTML, CSS, 相對視窗或螢幕的高度與寬度

在 w3school.com 網站, CSS Units 有各種與寬度的表示法 以我使用的頻率來排序: px: 使用螢幕幾個像素(但是還要考慮Retina螢幕像素是一般螢幕像素的兩倍) %: 相對父層的大小比例 vh, vw: 相對於瀏覽器展示網頁區域的大小(不是整個瀏覽器的大小,沒包含瀏覽器的工具列,只有展示網頁的區域) vmin: vh, vw取最小值(另外還有vmax則是取最大值,但是目前IE跟safari不支援) px 與 % 很常用, vh , vw 與 vmin 是CSS3的新產物,表示相對瀏覽器展示頁面的大小 相對視窗大小 這邊先說明, vh , vw 與 vmin 只包含網頁顯示區域的長寬,不包含瀏覽器的工具列 先從dom的最根本講起好了,一份HTML文件,根是 <html></html> (雖然沒有嚴格規定,不寫也能顯示),然後這個根的父元件就是瀏覽器的網頁頁面顯示區 因此,如果對 <html></html> 宣告大小是 100% 就跟宣告 100vh 一樣,因為都是指瀏覽器的網頁頁面顯示區大小 這個範例是將html設定為100vh <html style="height: 100vh"> <head> <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script> <script type="text/javascript"> console.log($('html').height()); </script> </head> </html> 這個範例是將html設定為100% <html style="height: 100%"> <head> <script src="https://code.jquery.com/jque...

陣列洗牌程式(shuffle array)

陣列如何把它的順序打亂,作出類似洗牌的效果,我一直都很頭痛,搞得非常的複雜。至從用了Ruby,Array物件包含 shuffle 方法之後,我就沒思考過陣列洗牌的問題了,反正Ruby幫我處理得好好的。 Ruby # 52張牌的牌堆 poker = (1..52).to_a # => [1, 2, 3, ... , 52] # 洗牌打亂 shuffled = poker.shuffle # => [22, 32, 12, ... # 也可以直接打亂原來的陣列 poker.shuffle! # => [39, 47, 3, ... Javascript Javascript我就頭痛了,我得自己寫洗牌的方法。我在網路上找到了這個演算法,仔細看了之後,才知道原來洗牌可以這麼簡單: // 原本for迴圈是一行程式,太難理解,這邊改寫成多行 function shuffle(o){ for(var j, x, i = o.length; i;){ j = Math.floor(Math.random() * i); // javascript的array是0-base // 所以迴圈第一次進入,--i後表示陣列最後一個位置。 x = o[--i]; o[i] = o[j]; o[j] = x; // 以上三行代表以x為temp, o[i], o[j]做交換 } return o; //回傳陣列,我一開始也看錯看成回傳0 }; 變數說明 引數o: 將被洗牌的陣列 for迴圈內 i : 將會從陣列的最後一個位置,慢慢往前移到第一個位置(但移到第一個位置時for迴圈不執行,因為Javascript的數值0也代表false,會離開迴圈。0代表flase這點跟Ruby不一樣) j : 將會被亂數選擇,選到要被交換的位置 x : 用來暫存o[i]的數值,幫助o[i]與o[j]做數值交換 就這樣從最後一個位置開始,依次往前隨機挑選一個位置與它交換(可能挑到自己,表示不交換),來達到洗牌的效果,陣列多大,就執行幾次,時間複雜度 O(n) ,...

linux, bash, find 的應用(-exec, sed -i, 檔案內取代, 與xargs比較)

find -exec find -exec 指的是將找到的檔案,送到後面的指令去處理。從 -exec 到 \; 為止,代表是接受從find送來要處理的指令,而送來的檔案將用 {} 代表找到的檔案。 譬如你想把所有副檔名為 .log 的檔案刪除掉,可以這麼做 find . -type f -name "*.log" -exec rm {} \; (其實我加上 -type f 有點多餘,因為我已經指定 -name "*.log" 了,就不可能輸出資料夾了。) 如果刪檔案的時候,一直要你按 yes ,可以這樣 yes | find . -type f -name "*.log" -exec rm {} \; yes 這個指令會一直串流輸出yes,這樣刪檔案就自動一直輸入yes 例子中 find 找到的檔案,就放到 -exec 至 \; 之間的指令去處理,譬如找到了檔案 develop.log ,就會變成 rm develop.log 另外, {} 並不是規定只能出現一次,譬如你要將資料夾內所有檔案加上副檔名 log ,可以這麼做 find . -type f -exec mv {} {}.log \; 這指令會包含子資料前內的檔案都加上.log副檔名。如果只想要目前資料夾的檔案,所以可以加上 -maxdepth 1 ,若要地回到下一層資料夾可以將1改成2,3或4以此類推 find . -maxdepth 1 -type f -exec mv {} {}.log \; 因此,若要批次改變檔案的內容,就可以搭配 find -exec 跟 sed -i 。sed 加上 -i 參數,代表直接對檔案內容做修改。 我常常在幫別人複製或移動網站,很多人的網址都寫成包含域名的絕對路徑,所以常常要用這個指令去找出所有含舊域名的檔案,並改成新域名。 譬如舊域名為 http://www.old.com ,要改成 http://www.new.com ,我會這麼做 find . -type f -exec sed -i 's/www.old.com/www.new.com/g' {} \; (sed這個指令在OS X上面如果照上例那樣執行,會有...