Monkey Patching in Ruby


Monkey Patching คือ Feature หนึ่งของภาษา Ruby
ที่อนุญาติให้เราเพิ่ม หรือ แก้ไข method ของ Class ที่ถูกประกาศไว้แล้วได้ ...
หรืออีกนัยหนึ่งเราอาจบอกได้ว่า เป็นการเพิ่มความสามารถให้ Class ที่มีอยู่แล้วนั่นเอง
โดย method ที่เกิดขึ้นจากการ Monkey Patch โดย Developer จะถูกเพิ่ม และเรียกใช้ขณะ Runtime
เช่นบน Rails เนี่ย เขาก็แก้ให้ Fixnum Class (Class ที่จัดการกับตัวเลขใน Ruby) 
สามารถเขียน 2.days.ago เพื่อคืนค่าเวลาของ 2 วันที่แล้วได้

ปัญหาที่เกิดขึ้นคือ ในเมื่อใครๆ ก็แก้ได้ เมื่อทำงานกันเป็นทีม ต้องคุยกันให้ดี
ว่าใคร Monkey Patch อะไรไว้บ้าง ... จะได้เกิดการชนกันของ method ที่เราเพิ่มหรือแก้ไป
(ตาม Bug ยากด้วย) ซึ่งใน Ruby 2.0 ก็มี Feature ที่มาแก้ปัญหานี้ได้ด้วยคือ Refinement

แต่ผมจะมาแนะนำ Feature นี้ในเชิงทั่วๆ ไป เช่น การเล่น Problem Solving
ที่ผมใช้ในเชิงว่าผมต้องการ Refactor Code ให้สวยขึ้น อ่านง่ายขึ้น

ตัวอย่างง่ายๆ เช่น ปกติถ้าผมต้องการเขียนฟังก์ชั่นที่ใช้ในการตรวจสอบความเป็นพาลินโดรมของ String สักสายนึง ก็จะเขียนแบบนี้
def is_palindrome(s)
  s == s.reverse
end

แล้วเรียกใช้โดยเขียนแบบนี้
s = "racecar"
if is_palindrome(s)
  puts "s is Palindrome"
else
  puts "s is not Palindrome"
end

ซึ่งมันก็พอรับได้แหละ แต่เนื่องจากการเป็น Dynamic Typing (ไม่รู้ type ของ s จริงๆ ถ้าส่ง s ที่เป็น Integer ไปก็จะพัง)
เราจะเห็นว่า ถ้ามีฟังก์ชั่นที่ต้องใช้งานเยอะๆ ก็คงจะเละเทะน่าดู และฟังก์ชั่นนี้เขียนมาเพื่อใช้งานกับ String เท่านั้น ผมก็เลยเลือกที่จะ Monkey Patch มัน

วิธีการง่ายๆ ก็คือเขียนบอกว่า เราจะทำ Monkey Patching กับ Class อะไร
จะเพิ่ม method อะไร และแทนตัวแปรที่จะมาเรียกใช้งานด้วย self

class String
  def is_palindrome
    self == self.reverse
  end
end

พอเวลาเรียกใช้เราก็เรียกผ่านตัวแปรที่เป็น String ได้ทันทีแบบนี้

s = "abbba"
if s.is_palindrome
  puts "s is Palindrome"
else
  puts "s is not Palindrome"
end

อีกตัวอย่างนะครับ คราวนี้ผมจะเขียน method เพื่อตรวจสอบว่าตัวเลขนั้นๆ เป็นจำนวนเฉพาะหรือไม่ ?

class Fixnum
  def root
    Math.sqrt(self)
  end

  def is_prime
    return false if self < 2
    2.upto(self.root.to_i) { |i| return false if self % i == 0 }
    return true
  end
end

เวลาใช้งาน อย่างเช่น ต้องการแสดงจำนวนเฉพาะที่อยู่ระหว่าง 1 - 100 ก็เขียนแบบนี้

1.upto(100) do |i|
  if i.is_prime
    print "#{i} "
  end
end

ทั้งนี้ สิ่งที่ควรระวังก็เรื่องการตั้งชื่อแหละครับ ถ้ามันไปซ้ำกับ method ที่มีอยู่ก็แปลว่าเราแก้ไข method ที่มีอยู่ และอย่าไปซ้ำกับพวก reserved word ทั้งหลาย ทั้งแหล่ ลองนำไปใช้ดูนะครับ ... ผมว่า Code จะดูสวยและเป็นระเบียบมากขึ้นเลยนะ

Popular posts from this blog

12 วิธี การบริการและดูแลลูกค้าในร้าน Starbucks

[Android Dev] การติดตั้ง Eclipse+AndroidSDK เพื่อพัฒนาโปรแกรมบน Android

"อีสุกอีใส" ประสบการณ์เมื่อต้องมาเป็นตอนอายุ 22