Modules ในภาษา Ruby
ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับโมดูลในภาษา Ruby เราจะพูดถึงการประกาศและนำโมดูลไปใช้งานในการเขียนโปรแกรม และแนะนำให้คุณรู้จักกับ Mix-in โมดูล ก่อนเริ่มมาทำความรู้จักกันก่อนว่าโมดูลคืออะไร และมันมีประโยชน์อย่างไรในการเขียนโปรแกรม
Modules คืออะไร
โมดูล (Modules) นั้นเป็นคอนเทนเนอร์ที่ใช้สำหรับเก็บข้อมูลประเภทอื่นไว้ภายใน ยกตัวอย่างเช่น คลาส เมธอด หรือค่าคงที่ คุณสามารถจินตนาการได้ว่าโมดูลนั้นเหมือนกับโฟล์เดอร์ เราสามารถใช้โมดูลในการจัดกลุ่มของโค้ดเข้าไว้ด้วยกันได้เพื่อให้ง่ายต่อการใช้งาน และช่วยให้การออกแบบโปรแกรมเป็นระบบมากขึ้น
ในภาษา Ruby เรามักจะใช้โมดูลเพื่อวัตถุประสงค์สองอย่างคือ ใช้ในการจัดกลุ่มของโค้ดเข้าไว้ด้วยกัน และใช้เป็น Mix-in โมดูลเพื่อประกาศเมธอดและนำไปใช้กับคลาส ต่อไปมาดูรูปแบบการประกาศโมดูลในภาษา Ruby
module ModuleName
# Definition
end
เราจะใช้คำสั่ง module
ในการประกาศ ตามด้วยชื่อของโมดูลและจบการประกาศด้วยคำสั่ง end
ชื่อของโมดูลควรจะขึ้นต้นด้วยตัวพิมพ์ใหญ่ในรูปแบบของ Camel Case และภายในโมดูลนั้นเราสามารถประกาศเมธอด ค่าคงที่ และคลาสไว้ข้างในได้ นอกจากนี้ เรายังสามารถประกาศโมดูลซ้อนกันได้
Creating modules
หลังจากที่คุณทราบแล้วว่าโมดูลคืออะไรและประกาศอย่างไร ต่อไปมาดูตัวอย่างการประกาศโมดูลในภาษา Ruby เราจะประกาศโมดูลของบริษัท ซึ่งโมดูลนี้จะเก็บคลาสสำหรับเก็บข้อมูลที่เกี่ยวกับบริษัทเอาไว้
module Company
class Employee
attr_accessor(:name, :salary, :department)
def initialize(name, salary, department)
@name = name
@salary = salary
@department = department
end
end
class Department
attr_accessor(:name)
def initialize(name)
@name = name
end
end
end
emp = Company::Employee.new("Mateo", 98000, "Engineering")
print "#{emp.name} works in #{emp.department} department"
puts " and his salary is #{emp.salary}"
departments = [
Company::Department.new("Engineering"),
Company::Department.new("Marketing"),
Company::Department.new("Customer Service")
]
puts "There are #{departments.length} departments in our company"
departments.each { |d|
puts d.name
}
ในตัวอย่าง เราได้ประกาศโมดูลที่ชื่อว่า Company
และเราได้ประกาศสองคลาสอยู่ข้างในโมดูล ได้แก่ คลาส Employee
เป็นคลาสของพนักงานในบริษัท และคลาส Department
เป็นคลาสของแผนกในบริษัท จะเห็นว่าเนื่องจากทั้งสองคลาสนั้นเป็นคลาสที่ใช้จัดการข้อมูลเกี่ยวกับบริษัท ดังนั้นเราจึงใช้โมดูลในการจัดเก็บทั้งสองคลาสนี้เอาไว้ด้วยกัน
emp = Company::Employee.new("Mateo", 98000, "Engineering")
...
departments = [
Company::Department.new("Engineering"),
Company::Department.new("Marketing"),
Company::Department.new("Customer Service")
]
หลังจากประกาศโมดูลเรียบร้อยแล้ว เรานำคลาสในโมดูลมาใช้งานในการสร้างออบเจ็ค เราสามารถเข้าถึงคลาสในโมดูลด้วยชื่อของโมดูล ตามด้วยเครื่องหมายโคลอน (::
) และตามด้วยชื่อของคลาส ในรูปแบบ ModuleName::ClassName
Mateo works in Engineering department and his salary is 98000
There are 3 departments in our company
Engineering
Marketing
Customer Service
นี่เป็นผลลัพธ์การทำงานของโปรแกรม ในการประกาศและใช้งานโมดูลในภาษา Ruby
การประกาศเมธอดในโมดูล
อย่างที่เราได้บอกไปในตอนต้นว่านอกจากคลาสแล้ว คุณยังสามารถประกาศเมธอดและค่าคงที่ไว้ในโมดูลได้ แต่สำหรับการเรียกใช้งานเมธอดในโมดูลจะแตกต่างจากคลาส เราจะต้องเข้าถึงเมธอดในโมดูลในรูปแบบ ModuleName.method_name
แทน และเราจะต้องนำเข้าโมดูลด้วยเมธอด include
ก่อนจึงจะสามารถใช้งานเมธอดในโมดูลได้ นี่เป็นตัวอย่าง
module M
def f
puts "method f"
end
def g
puts "method g"
end
end
include M
M.f
M.g
ในตัวอย่าง เราได้ประกาศโมดูล M
ภายในโมดูลนั้นมีสองเมธอดอยู่ภายในคือเมธอด f
และ g
ในการใช้งานเมธอดในโมดูลนั้น เราจะใช้เมธอด include
ในการโหลดโมดูลเข้ามาในโปรแกรมก่อนที่เราจะสามารถเรียกใช้งานเมธอดทั้งสองได้
method f
method g
นี่เป็นผลลัพธ์การทำงานของโปรแกรม ในการประกาศและเรียกใช้งานเมธอดที่ถูกประกาศไว้ในโมดูล
การนำเข้าโมดูลจากไฟล์อื่นมาใช้งาน
ในตัวอย่างก่อนหน้า เราได้สร้างโมดูลไว้ในไฟล์เดียวกับการเรียกใช้ อย่างไรก็ตาม วิธีที่ดีที่สุดในการประกาศโมดูลก็คือสร้างมันเอาไว้อีกไฟล์ และใช้เมธอด require
ในการโหลดโมดูลเข้ามาใช้งานในโปรแกรม นี่จะทำให้เราสามารถนำโมดูลนั้นกลับมาใช้ใหม่ในหลายๆ โปรแกรมได้ และมันยังเป็นวิธีในการจัดระเบียบโค้ดที่ดีอีกด้วย
ตัวอย่างต่อไป เราจะสร้างโมดูลที่เก็บคลาสเกี่ยวกับรูปร่าง นอกจากนี้เรายังจะประกาศเมธอดและค่าคงที่ภายในโมดูลอีกด้วย นี่เป็นโค้ดตัวอย่าง
module Shape
DRAW_CHAR = "#"
class Rectangle
attr_accessor(:width, :height)
def initialize(width, height)
@width = width
@height = height
end
def area
return @width * @height
end
end
class Triangle
attr_accessor(:width, :height)
def initialize(width, height)
@width = width
@height = height
end
def area
return @width * @height / 2
end
end
def draw_rectangle(object)
for i in (0...object.height)
for j in (0...object.width)
print DRAW_CHAR
end
puts
end
end
def draw_triangle(object)
w = object.width.to_f
h = object.height.to_f
for i in (0...h)
for j in (0...w)
if j >= (w / 2) - (w / h / 2 * i) - 1 &&
j <= w / 2 + (w / h / 2 * i)
print DRAW_CHAR
else
print "-"
end
end
puts
end
end
end
ในตัวอย่าง เราได้สร้างโมดูลที่ชื่อว่า Shape
ซึ่งโมดูลนี้มีสองคลาสอยู่ภายในคือคลาส Rectangle
เป็นคลาสของรูปสี่เหลียม และคลาส Tritangle
เป็นคลาสของรูปสามเหลี่ยมหน้าจั่วซึ่งเป็นรูปสามเหลี่ยมที่มีด้านสองด้านที่เท่ากัน
DRAW_CHAR = "#"
สำหรับโค้ดบรรทัดแรกในโมดูล เราได้ประกาศค่าคงที่ DRAW_CHAR
ซึ่งเป็นค่าคงที่สำหรับเก็บตัวอักษรที่เอาไว้ใช้วาดรูปสี่เหลี่ยมและรูปสามเหลี่ยมที่สร้างไปจากคลาสที่เราได้บอกไปก่อนหน้า
def draw_rectangle(object)
...
end
def draw_tritangle(object)
...
end
ในตอนท้ายของโมดูล เราได้ประกาศเมธอดสองเมธอดสำหรับวาดรูป เมธอด draw_rectangle
ใช้สำหรับวาดรูปของออบเจ็คที่สร้างจากคลาส Rectangle
ส่วนเมธอด draw_tritangle
ใช้สำหรับวาดรูปของออบเจ็คที่สร้างจากคลาส Tritangle
ภายในเมธอดเราใช้คำสั่ง for loop เพื่อวนวาดรูปและใช้ตัวอักษรจากค่าคงที่ DRAW_CHAR
ในการวาด
หลังจากที่เราประกาศโมดูลเสร็จเรียบร้อยแล้ว ต่อไปเราจะสร้างไฟล์ใหม่เพื่อมาเรียกใช้งานโมดูล Shape
ของเรา และนี่เป็นโค้ดของโปรแกรม
require "./shape"
include Shape
r1 = Shape::Rectangle.new(8, 4)
r2 = Shape::Rectangle.new(4, 4)
t1 = Shape::Triangle.new(11, 5)
t2 = Shape::Triangle.new(8, 8)
puts "Area of a rectangle: #{r1.area}"
Shape.draw_rectangle(r1)
puts "Area of a rectangle: #{r2.area}"
Shape.draw_rectangle(r2)
puts "Area of a triangle: #{t1.area}"
Shape.draw_triangle(t1)
puts "Area of a triangle: #{t2.area}"
Shape.draw_triangle(t2)
puts "All shapes are drawn with #{Shape::DRAW_CHAR} character"
ในตัวอย่าง เราได้สร้างไฟล์ใหม่ที่มีชื่อว่า use_shape.rb
เพื่อเรียกใช้งานโมดูลก่อนหน้าที่เราได้ประกาศไปในไฟล์ shape.rb
และสมมติว่าเราวางไฟล์ทั้งสองนี้ไว้ในโฟล์เดอร์เดียวกัน
require "./shape"
include Shape
สิ่งแรกที่ต้องทำก่อนนำโมดูลมาใช้งานก็คือโหลดมันเข้ามาในไฟล์ปัจจุบันก่อน เราสามารถใช้เมธอด require
เพื่อโหลดไฟล์โปรแกรมในภาษา Ruby เข้ามาในโปรแกรมปัจจุบันของเราได้ นี่จะทำให้เหมือนกับว่าเราประกาศโมดูลไว้ในไฟล์เดียวกัน หลังจากนั้นเราใช้เมธอด include
เพื่อนำเข้าเมธอดในโมดูลมาใช้งาน
r1 = Shape::Rectangle.new(8, 4)
r2 = Shape::Rectangle.new(4, 4)
t1 = Shape::Triangle.new(11, 5)
t2 = Shape::Triangle.new(8, 8)
จากนั้นเราได้นำคลาส Rectangle
และ Triangle
ในโมดูลมาสร้างออบเจ็คของรูปสี่เหลี่ยมและรูปสามเหลี่ยมหน้าจั่ว เราเข้าถึงคลาสเหมือนกับที่เราทำในตัวอย่างก่อนหน้านั่นคือ MouleName::ClassName
นั่นเอง
puts "Area of a rectangle: #{r1.area}"
Shape.draw_rectangle(r1)
puts "Area of a rectangle: #{r2.area}"
Shape.draw_rectangle(r2)
puts "Area of a triangle: #{t1.area}"
Shape.draw_triangle(t1)
puts "Area of a triangle: #{t2.area}"
Shape.draw_triangle(t2)
puts "All shapes are drawn with #{Shape::DRAW_CHAR} character"
สิ่งที่เพิ่มเติมเข้ามาในตัวอย่างนี้ก็คือเราได้ประกาศค่าคงนี้ไว้ในโมดูลด้วย เราสามารถเข้าถึงค่าคงที่ภายในโมดูลดังในรูปแบบ MouleName::Constant
ซึ่งจะเหมือนกันกับการเข้าถึงคลาสภายในโมดูล
Area of a rectangle: 32
########
########
########
########
Area of a rectangle: 16
####
####
####
####
Area of a triangle: 27
-----#-----
----###----
---#####---
--#######--
-#########-
Area of a triangle: 32
---##---
---##---
--####--
--####--
-######-
-######-
########
########
All shapes are drawn with # character
นี่เป็นผลลัพธ์การทำงานของโปรแกรม ในตัวอย่างนี้แสดงให้คุณเห็นว่าเราสามารถประกาศโมดูลไว้อีกไฟล์ และนำเข้ามาใช้ในโปรแกรมใหม่ได้ ในขณะเดียวกัน เรายังสามารถประกาศเมธอดและค่าคงที่ไว้ในโมดูลเพื่อนำมาใช้งานได้อีกด้วย
Nested modules
ในภาษา Ruby เราสามารถประกาศโมดูลที่ซ้อนกันได้ การซ้อนกันของโมดูลจะทำให้เราสามารถจัดกลุ่มของโค้ดย่อยลงไปได้อีก ยกตัวอย่างเช่น
module M
module SUB1
class A
end
class B
end
end
module SUB2
C = 1
def f
end
end
end
ในตัวอย่าง เราได้ประกาศโมดูล M
ซึ่งมีสองโมดูลย่อยอยู่ข้างในคือโมดูล SUB1
และ SUB2
นี่จะทำให้เราสามารถแยกระหว่างการประกาศคลาสและเมธอดออกจากกันได้ ในขณะที่ยังแชร์โมดูลหลักร่วมกันคือโมดูล M
นั่นเอง และจากโมดูลของเรา เราสามารถเข้าถึงสิ่งที่ประกาศไว้ในโมดูลได้ดังนี้
# Using class
M::SUB1::A
M::SUB1::B
# Using constant
M::SUB2::C
# Using method
include M::SUB2
M::SUB2.f
และอย่างที่คุณรู้ ในการใช้งานคลาสและค่าคงที่นั้นสามารถเข้าถึงได้โดยตรง ส่วนการใช้งานเมธอดภายในโมดูลเราจะต้องนำเข้ามาด้วยเมธอด include
ก่อน
Mix-in modules
Mix-in โมดูลคือการประกาศเมธอดไว้ในโมดูลและเรียกใช้งานโดยคลาส นั่นจะทำให้เมธอดในโมดูลเป็นเหมือนกับว่ามันถูกประกาศไว้ในคลาส การใช้ Mix-in โมดูลจะทำให้คลาสสามารถใช้เมธอดบางอย่างร่วมกันได้ และเมธอดเหล่านั้นจะถูกเปลี่ยนบริษทการทำงานให้เข้ากับคลาสนั้นๆ ในตอนที่โปรแกรมทำงาน
ตัวอย่างของ Mix-in โมดูลในภาษา Ruby ก็คือ Enumerable
โมดูล โมดูลนี้ประกอบไปด้วยเมธอดสำหรับวนและค้นหาข้อมูลเป็นจำนวนมาก และมีหลายคลาสที่นำโมดูลนี้เข้าไปใช้งาน เช่น คลาส Array
Hash
และ Range
ยกตัวอย่างเช่น
a = [1, 2, 3, 4, 5]
h = { "th": "Thailand" , 'jp': "Japanse" }
r = (1..10)
a.each { |n|
puts n
}
h.each_value { |v|
puts v
}
puts a.min
puts a.max
puts r.min
puts r.max
เราสามารถเรียกใช้งานเมธอดสำหรับวนซ้ำ each
และ each_value
และเมธอดสำหรับหาค่าน้อยสุดและมากสุดภายในออบเจ็คของอาเรย์ แฮช และ Range ซึ่งเมธอดเหล่านี้เป็นเมธอดที่กำหนดใน Enumerable
โมดูล
ต่อไปมาดูตัวอย่างการใช้งาน Mix-in โมดูลในการเขียนโปรแกรม เราจะสร้างคลาสของวิดีโอและเสียงโดยการใช้ประโยชน์จาก Mix-in โมดูล นี่เป็นโค้ดของโปรแกรม
module Controllable
def play
puts "#{self.class.name} is playing"
end
def pause
puts "#{self.class.name} has paused"
end
def stop
puts "#{self.class.name} has stopped"
end
end
class Video
include Controllable
def render_image
puts "Rendering video image"
end
end
class Audio
include Controllable
end
v = Video.new
v.play
v.render_image
v.pause
v.stop
a = Audio.new
a.play
a.pause
a.stop
ในตัวอย่าง เราได้สร้างโมดูลที่มีชื่อว่า Controllable
โดยโมดูลนี้ประกอบไปด้วยสามเมธอดคือ play
pause
และ stop
สำหรับควบคุมการเล่น หยุดพัก และหยุดเล่นตามลำดับ หลังจากนั้นเราได้สร้างสองคลาสที่นำโมดูลนี้ไปใช้งาน สิ่งที่สามารถสังเกตได้ในตัวอย่างนี้ก็คือแทนที่เราจะประกาศเมธอดเหล่านี้ไว้ในแต่ละคลาส แต่เราประกาศไว้ในโมดูลและนำมันมาเรียกใช้งานแทน นี่จะทำให้เราสามารถสร้างเมธอดเพียงครั้งเดียว และนำไปใช้กับกี่คลาสก็ได้ที่ต้องการมีเมธอดดังกล่าว
def play
puts "#{self.class.name} is playing"
end
ภายในเมธอดเราได้อ้างถึงตัวแปร self
มันเป็นตัวแปรที่อ้างถึงออบเจ็คปัจจุบันที่เรียกใช้งานเมธอดนี้อยู่ ยกตัวอย่างเช่น เมื่อเราเรียกใช้งานเมธอดจากออบเจ็คของคลาส Video
ตัวแปรนี้จะเป็นออบเจ็คของคลาส Video
และเช่นเดียวกันกับคลาส Audio
หลังจากนั้นเราใช้เมธอด name
เพื่อรับเอาชื่อคลาสออกมาแสดงผล นี่แสดงให้เห็นว่าการทำงานของเมธอดภายในโมดูลจะเปลี่ยนไปตามบริษทของคลาสที่มันทำงานอยู่
class Video
include Controllable
def render_image
puts "Rendering video image"
end
end
class Audio
include Controllable
end
เราได้สร้างคลาส Video
และ Audio
โดยการนำเข้าโมดูล Controllable
มาใช้งานเนื่องจากเราต้องคลาสให้คลาสทั้งสองมีเมธอดสำหรับการเล่น การหยุดพัก และการหยุดเล่น ที่เราได้ประกาศไว้แล้วในโมดูล
การทำเช่นนี้เป็นการผูกเมธอดภายในโมดูลเข้ากับคลาส นั่นทำให้ทั้งคลาส Video
และ Audio
มีเมธอด play
pause
และ stop
เหมือนกับว่าเราประกาศเมธอดภายในคลาสเหล่านี้ นอกจากนี้ในคลาส Video
ก็ยังมีเมธอด render_image
เป็นของมันเอง เนื่องจากวิดีโอนั้นต้องมีการแสดงผลภาพด้วย
Video is playing
Rendering video image
Video has paused
Video has stopped
Audio is playing
Audio has paused
Audio has stopped
นี่เป็นผลลัพธ์การทำงานของโปรแกรมในการประกาศและนำ Mix-in โมดูลมาใช้งานกับคลาสในภาษา Ruby
def render_image
puts "Rendering video image"
end
อีกตัวอย่างของ Mix-in โมดูลที่ใช้ในภาษา Ruby ก็อย่างเช่น เราสามารถเรียกใช้งานเมธอด puts
ได้ในทุกคลาสถึงแม้ว่าภายในคลาสเหล่านั้นจะไม่ได้ประกาศเมธอดดังกล่าว นั่นเป็นเพราะว่าทุกคลาสในภาษา Ruby นั้นสืบทอดมาจากคลาส Object
และคลาสออบเจ็คนั้นนำเข้าเมธอดจากโมดูล Kernel
มาใช้งาน ดังนั้น โมดูล Kernel
จึงเป็น Mix-in โมดูลในภาษา Ruby
ในบทนี้ คุณได้เรียนรู้เกี่ยวกับโมดูลในภาษา Ruby เราได้ประกาศและนำโมดูลมาใช้งาน และแนะนำการใช้งาน Mix-in โมดูล ซึ่งคือความสามารถในการนำเมธอดไปใช้งานกับหลายๆ คลาสได้โดยการประกาศไว้เพียงทีเดียว