Ruby มีความสามารถที่เรียกว่า built-in conversion ที่ช่วยให้เมธอดสามารถ เปลี่ยน (convert) อ็อบเจกต์ที่ป้อนเข้ามา ให้เป็นค่าของข้อมูลพื้นฐานอย่างเช่น ตัวเลข สตริงหรืออาร์เรย์ ตามที่เมธอดนั้นคาดหวังได้<br /> คำอธิบายข้างต้นอาจทำให้ไม่เห็นภาพ ลองดูตัวอย่างตามนี้ ครับ<br /> สมมติว่าเรามี อาร์เรย์
grade
ซึ่งประกอบด้วยชื่อระดับราคาและความหรูหราของโรงแรม ตั้งแต่ โมเต็ล จนถึง โรงแรมระดับลักเซอร์รี่ เราต้องการแสดงข้อความที่ระบุชื่อของโรงแรมจากอ็อบเจกต์ hotel
และพร้อมบอกระดับชั้น ตามจำนวนของดาว ซึ่งแทนด้วย rating
ที่เป็น attribute ของ hotel
โดย rating
จะเป็นตัวเลข มีค่าตั้งแต่ 0 ถึง 5
เราสามารถสร้างอ็อบเจกต์ hotel ง่ายๆ ได้โดยใช้ Struct ดังนี้Hotel = Struct.new(:name, :rating, :address)
h1 = Hotel.new("Sweet Inn", 1, "Bangkok")
h2 = Hotel.new("Centrara Grand", 5, "Phuket")
กำหนดให้ระดับมาตรฐานและบริการ (ความไฮโซ) ของ hotel อยู่ในอาร์เรย์ที่ชื่อ grade
grade = ["Motel", "Bed and breakfasts", "Contel", "Boutique", "Resort", "Luxury"]
เราสามารถแสดงข้อความที่ต้องการได้ดังนี้
[h1, h2].each do |hotel|
puts "#{hotel.name} at #{hotel.address} is rating as #{grade[hotel.rating]} hotel."
end
# = > Sweet Inn at Bangkok is rating as Bed and breakfasts hotel
# = > Centrara Grand at Phuket is rating as Luxury hotel
ข้อความข้างต้นที่ได้นั้นเป็นไปตามที่เราต้องการแล้ว
แต่เมื่อลองพิจารณาที่โค้ดดูอีกครั้ง ก็พบว่าตรง
grade[hotel.rating]
นั้น ค่าของ hotel.rating
ต้องเป็นตัวเลขเท่านั้น ซึ่งทำหน้าที่เป็นตัวชี้ลำดับของสมาชิกภายในอาร์เรย์ gradeในกรณีแบบนี้ เราสามารถกำหนดให้อ็อบเจกต์ hotel ทำหน้าที่ตัวชี้ สมาชิกของอาร์เรย์ด้วยตัวของมันเอง โดยไม่ต้องเรียกใช้เมธอด rating หรือเรียกอีกอย่างว่าการทำ type conversion
ก่อนอื่นให้เราเพิ่มเมธอด
to_int
ให้กับ Hotel
ดังนี้Hotel = Struct.new(:name, :rating, :address) do
def to_int
rating
end
end
เพียงเท่านี้ เราก็สามารถหาค่าของระดับชั้นจากอาเรย์
grade
ได้จากอ็อบเจกต์ hotel
ได้ตรงๆ ดังนี้grade[h1] # => Motel
grade[h2] # => Luxury
ทำไมอาร์เรย์ถึงยังสามารถคืนค่าออกมาได้อย่างถูกต้อง ?
ค่าที่อยู่ใน
[ ]
ควรที่จะเป็นตัวเลขตรงๆ แทนที่จะเป็นอ็อบเจกต์ไม่ใช่หรือ ?คำตอบอยู่ที่กระบวนการจัดการกับประเภทของข้อมูลที่เกิดขึ้นภายในคลาสมาตรฐานของ Ruby ซึ่งจะเห็นได้จากคลาส
Array
จากตัวอย่างที่ผ่านมา คลาสอาร์เรย์จะพยายามแปลงข้อมูลให้อยู่ในรูปที่ตัวมันเองคาดหวังเอาไว้ มันจึงส่งเมสแสส to_int
ให้กับอ็อบเจกต์ใดๆ ก็ตามที่ถูกป้อนเข้ามาในเมธอด []
เสมอ ถ้าอ็อบเจกต์ที่ป้อนเข้ามเป็นอ็อบเจกต์ของคลาส Fixnum (คลาสสำหรับตัวเลขจำนวนเต็ม) เมื่อส่งเมสแสส to_int
ไปให้ ค่าที่ได้กลับก็จะยังคงเป็นตัวเลขจำนวนเต็ม อาร์เรย์ก็จะทำงานได้ตามปกติ แต่ถ้าอ็อบเจกต์ที่ถูกป้อนเข้ามาในเมธอด []
เป็นสตริงหรืออ็อบเจกต์จากคลาสอื่นๆ สิ่งที่เกิดขึ้นก็คือ เราจะเจอกับ error message เนื่องจากอ็อบเจกต์ที่ป้อนเข้ามาไม่ตอบสนองกระบวนการแปลงข้อมูลไปเป็นตัวเลขภายในคลาสอาร์เรย์ นั่นคือไม่มีการตอบสนองกับเมสแสส to_int
ที่คลาสอาร์เรย์ส่งให้กับอ็อบเจกต์นั่นเองเมื่อลองทดสอบพฤติกรรมดังกล่าวโดยใช้ irb คุณจะเห็นว่าข้อความ error จะระบุชัดเลยว่า argument ที่ป้อนให้กับเมธอด
[]
ของอาร์เรย์นั้นไม่ตอบสนองกับ to_int>> a = ["cat", "bat", "rat"]
=> ["cat", "bat", "rat"]
>> a[0]
=> "cat"
>> a["zero"]
TypeError: no implicit conversion of String into Integer
from (irb):8:in `[]'
from (irb):8
from C:/Ruby200/bin/irb:12:in
ทีนี้กลับมาดูที่กรณีตัวอย่างของ อาเรย์
grade
กับอ็อบเจกต์ Hotel
อีกครั้ง ถ้าเราป้อนอ็อบเจกต์ที่าสามารถตอบสนองกับเมสแสส to_int
ได้ โดยคืนค่าเป็นตัวเลขจำนวนเต็มออกมาตามที่อาร์เรย์คาดหวังไว้แล้วล่ะก็ เมธอด []
ของอาร์เรย์ก็จะสามารถทำงานได้อย่างปกติ ดังที่เห็นจากการป้อนอ็อบเจ็กต์ h1 และ h2 ให้กับเมธอด []
ของอาร์เรย์ grade
ข้างต้นนั่นเองเราเรียกเมธอดที่ทำหน้าที่ในการแปลงประเภทของข้อมูลในลักษณะนี้ว่าเมธอดแบบ implicit ตัวอย่างเช่น
to_int
(คืนค่าเป็นอ็อบเจกต์ของคลาส Integer), to_str
(คืนค่าเป็นอ็อบเจกต์ของคลาส String), to_ary
(คืนค่าเป็นอ็อบเจกต์ของคลาส Array), to_path
(คืนค่าเป็นอ็อบเจกต์ของคลาส String), to_io
(คืนค่าเป็นอ็อบเจกต์ของคลาส IO) เป็นต้นImplicit vs. Explicit
Ruby มีเมธอดอีกประเภทหนึ่งที่ทำหน้าที่แปลงข้อมูลเหมือนกัน เรียกว่าเมธอดแบบ Explicit ได่แก่
to_s
, to_i
, to_a
เมธอด Explicit ทั้งสามตัวนี้ทำหน้าที่แปลงข้อมูลไปเป็นข้อมูลประเภท String, Integer และ Array ตามลำดับ แต่การใช้งานจะต่างกันกับ to_str
, to_int
และ to_ary
สำหรับเมธอดแบบ Explicit จะใช้แปลงค่าของอ็อบเจกต์ที่เราสนใจไปเป็นค่าของข้อมูลในประเภทที่เราต้องการ เช่น
to_s
เพื่อแปลงให้เป็น String และ to_i
เพื่อแปลงเป็น Integer เมธอดแบบ Explicit อย่าง to_s
หรือ to_i
โดยทั่วไปมักจะถูกกำหนดไว้เป็นหนึ่งในเมธอดของคลาสต่างๆ เพื่อคืนค่าสตริงหรือตัวเลขสำหรับอ็อบเจกต์ของคลาสนั้นๆ เช่นคลาส String มีเมธอด to_i เอาไว้แปลงค่าของสตริงที่เป็นตัวเลขอย่าง "3".to_i
จะได้ค่าของตัวเลข 3 ออกมาแต่สำหรับเมธอดแบบ Implicit อย่างเช่น
to_str
หรือ to_int
ทำหน้าที่แปลงข้อมูลเหมือนกันก็จริงแต่มันจะถูกกำหนดอยู่ในคลาสของตัวเองเท่านั้น เช่น to_int
จะเรียกใช้ได้จากอ็อบเจกต์ของคลาส Integer นั้น ถ้าเราใช้ "3".to_int
สิ่งที่ได้จะเป็น error message เพราะ คลาสสตริงไม่ได้กำหนดเมธอด to_int
เอาไว้ (มีแต่ to_i
) และเนื่องจากคลาสมาตรฐานของ Ruby มักมีการเรียกใช้เมธอดแบบ Implicit กับอ็อบเจกต์ที่เป็น argument ของเมธอดในคลาสมาตรฐานนั้นๆ ทำให้ตรงนี้เป็นช่องให้โปรแกรมเมอร์สามารถที่จะเลือกได้ว่าต้องการให้อ็อบเจกต์จากคลาสที่ตนสร้างขึ้นตอบสนองกับเมธอดของคลาสมตรฐานโดยการ override เมธอดแบบ implicitสรุปความแตกต่างก็คือเมธอดแบบ Explicit นั้นส่วนใหญ่มีการกำหนดอยู่ในคลาสทั่วๆ ไป ใช้เพื่อแปลงค่าของอ็อบเจกต์จากคลาสเดิมไปเป็นค่าของประเภทข้อมูลที่ต้องการ โดยคลาสเดิมกับคลาสของประเภทของข้อมูลที่ต้องการนั้นเป็นคนละคลาสกันเลย ส่วนเมธอดแบบ Implicit นั้นใช้เพื่อทดสอบว่าอ็อบเจกต์นั้นๆ ตอบสนองออกมาเป็นข้อมูลประเภทที่คาดหวังไว้หรือไม่ คลาสมาตรฐานของ Ruby มักจะส่งเมสแสส ที่เป็นชื่อของเมธอดแบบ Implicit เพื่อทดสอบว่ามันกำลังทำงานกับประเภทของข้อมูลที่มันคาดหวังเอาไว้
ลองดูตัวอย่างและความแตกต่างระหว่างเมธอด Explicit และ Implicit อีกทีจากตัวอย่างต่อไปนี้
>> 3.to_s
=> "3"
>> 3.to_str
NoMethodError: undefined method `to_str' for 3:Fixnum
from (irb):31
from C:/Ruby200/bin/irb:12:in `'
>> [1,2,3].to_s
=> "[1, 2, 3]"
>> [1,2,3].to_str
NoMethodError: undefined method `to_str' for [1, 2, 3]:Array
from (irb):34
from C:/Ruby200/bin/irb:12:in `'
>> "3".to_str
=> "3"
จะเห็นว่า
to_s
ซึ่งเป็น Explicit เมธอดนั้นมักจะถูกกำหนด (implement) อยู่ในคลาสต่างๆ ในขณะที่ to_str
คู่ของมันซึ่งเป็น Implicit เมธอดจะถูกกำหนดอยู่ในคลาสที่เป็นข้อมูลประเภทเดียวกันเท่านั้น (ที่นี้คือคลาส String) จะไม่มีการกำหนดอยู่ในคลาสอื่น (เราต้องไปกำหนดเองถ้าต้องการ)ลองดูเมธอด
+
ของคลาส String ในตัวอย่างต่อมา>> "2" + "3"
=> "23"
>> "2" + 3
TypeError: no implicit conversion of Fixnum into String
from (irb):36:in `+'
from (irb):36
from C:/Ruby200/bin/irb:12:in `'
เมธอด +
คาดหวังว่า argument ของมันต้องเป็นสตริงและคลาสตริงก็มีการตรวจสอบและพยายามแปลงข้อมูลโดยการส่งเมสแสส to_str
ซึ่งเป็นเมธอดแบบ Implicit ดังนั้น "2" + 3
จึงเกิด error เพราะเราไม่ได้กำหนดเมธอด to_str
ให้กับคลาส Integer
แต่ถ้าเรามีอ็อบเจกต์ที่ตอบสนองกับ to_str
อ็อบเจเราก็สามารถป้อนอ็อบเจกต์นั้นให้กับเมธอด +
ของสตริงได้ดังนี้>> class Team
>> def initialize(name)
>> @name = name
>> end
>> def to_str
>> @name
>> end
>> end
=> nil
>> team = Team.new("Arsenal")
=> #
>> "2" + team
=> "2Arsenal"
เมื่อลองส่งเมแสส
to_s
ให้กับ team
จะได้สตริงเป็น object id ของ team
นั้นเป็นเพราะเมธอด to_s
ถูกกำหนดมากับคลาส Class
ตั้งแต่แรกแล้ว แต่สำหรับเมธอด to_str
เรากำหนดให้กับ team เป็นการเฉพาะก็พอสมควรนะครับสำหรับ post นี้หวังว่าจะได้ idea สำหรับนำไปปรับใช้ในโค้ดได้บ้าง
สำหรับท่านที่ยังคงสนใจรายละเอียดของเรื่อง Built In Conversion ผมแนะนำให้หาอ่านได้จาก Confident Ruby ของ Advi Grimn ซึ่งถือว่าเป็นหนังสือที่ดี และอยากแนะนำครับ
ไม่มีความคิดเห็น:
แสดงความคิดเห็น