2014年7月26日 星期六

Ruby: class << self

Ruby: class << self

我們先不管class << self是什麼意思,我們先來看看動態語言有哪些有趣的用法。

Ruby可以動態的在class裡面新增,修改一些方法。譬如:

class Foo
  def foo
    return 'Hello Foo'
  end
end

f = Foo.new
puts f.foo # Hello Foo

class Foo
  def foo
    return 'Edit Foo'
  end
end
puts f.foo # Edit Foo

有玩過RPG Maker的人應該很熟練了,RPG Maker的模組都是這樣掛上去的,直接把模組複製到整個Script的最後面,將系統原先的方法override掉(這邊我講override其實有點問題,因為override比較適合用在編譯時期,子類別繼承並修改父類別的方法,但這裡單純只是修改類別原先的方法)

好的,那如果我產生兩個Foo物件,延續上例的類別

class Foo
  def foo
    return 'Hello Foo'
  end
end

f1 = Foo.new
f2 = Foo.new
puts f1.foo # Hello Foo
puts f2.foo # Hello Foo

class Foo
  def foo
    return 'Edit Foo'
  end
end

puts f1.foo # Edit Foo
puts f2.foo # Edit Foo

因為是直接對類別做修改,所以上例中,f1與f2的行為都會改變。

那如果我們今天想要做出像Javascript那種功能,只對單一物件新增或修改方法,有辦法做到嗎?譬如Javascript可以這麼做:

function Foo(){
}

Foo.prototype.foo = function(){
  return "Hello foo";
}

var f1 = new Foo();
var f2 = new Foo();

console.log( f1.foo() ); // Hello foo
console.log( f2.foo() ); // Hello foo

// 只對物件f1修改方法

f1.foo = function(){
  return 'Edit foo';
}

console.log( f1.foo() ); // Edit foo
console.log( f2.foo() ); // Hello foo

// 只對物件f1新增方法

f1.bar = function(){
  return 'Hello bar';
}

console.log( f1.bar() ); // Hello bar
console.log( f2.bar() ); // ERROR, undefined

這麼動態呀~那Ruby呢?Ruby有沒有辦法做到,Ruby有個語法,看起來很像直接對單一物件,宣告出一個類別

class << some_object

直接來個可以執行的範例

class Foo
  def foo
    return 'foo'
  end
end

f1 = Foo.new
f2 = Foo.new

puts f1.foo # foo
puts f2.foo # foo

# 只對f1 修改方法
class << f1
  def foo
    return 'Edit foo'
  end
end

puts f1.foo # Edit foo
puts f2.foo # foo

# 只對f1 新增方法
class << f1
  def bar
    return 'bar'
  end
end

puts f1.bar # bar
puts f2.bar # undefined method `bar'

好的,到這裡,回到我們的標題class << self,這是什麼意思?可以用在什麼地方呢?

還記得Ruby每個東西都是物件,我們要對類別增加類別方法是怎麼做的嗎?

class Foo
  def self.foo
    return 'foo'
  end
end

puts Foo.foo #foo

直接在類別裡,並非在物件會被呼叫到的地方使用self,則會指向Foo這個Class物件,所以我們直接對Foo這個Class物件加上方法,就會變成Foo的類別方法。

有沒有發現跟上面我們對單一物件新增或修改方法有點像?是的,既然Foo是個單一的Class物件,我們也可以單一的為它加上方法,成為類別方法。

class Foo
  class << self
    def foo
      return 'foo'
    end
  end
end

class Bar
end

puts Foo.foo #foo
puts Bar.foo #undefined method `foo' for Bar:Class (NoMethodError)

foo加到Foo類別的成為它的類別方法,所以不影響Bar類別

主要是學習Ruby語言的一個動態特性,使用簡潔的程式碼達到這些目的。就跟JAVA 8也做出Lambda Function了一樣。

那麼實際上有什麼應用呢?譬如Design Pattern裡面的Visitor Pattern在Ruby裡面可以這麼應用:

class Good
  attr_accessor :price

  def initialize price
    @price = price
  end

  def accept visitor
    visitor.visit self
  end
end

class TaxVisitor

  def initialize rate
    @rate = rate
  end

  def visit obj
    if obj.respond_to? :price

      class << obj
        attr_accessor :tax
      end

      obj.tax = obj.price * @rate
    end
  end
end

good = Good.new 30
good.accept(TaxVisitor.new(0.25))
puts good.tax

我們對Good類別,accept稅金的訪問,然後就在訪問時,動態的為貨物加上了tax這個方法,可以對貨物存取稅金。這個範例寫得不太好,其實price與tax應該要對外關閉,寫成唯讀才對


另外,物件也可以動態的掛載module

module Foo
    def foo
        puts 'foo'
    end
end

obj1 = Object.new
obj2 = Object.new

obj1.extend Foo

obj1.foo # => foo
obj2.foo # => 錯誤,NoMethodError

沒有留言:

張貼留言