หน้าเว็บ

วันอังคารที่ 11 สิงหาคม พ.ศ. 2552

[ภาษา Ruby] OOP ตอนที่ 1 Class และ Object

Object Oriented Programming

การเขียนโปรแกรมแบบ OOP(Object Oriented Programming) หรือที่เรียกกันว่าการเขียนโปรแกรมเชิงวัตถุนั้น คือ ”รูปแบบ” ของการเขียนโปรแกรมอย่างหนึ่งที่บังคับ(แบบอ้อมๆ)ให้เราในฐานะผู้เขียนโปรแกรมต้องออกความคิดให้รอบคอบก่อนลงมือเขียน คือต้องคิดก่อนว่าโปรแกรมที่เราจะสร้างขึ้นมานั้นจะประกอบด้วยอะไร และจะเอามันไปใช้ทำอะไรบ้าง ซึ่งในขั้นตอนนี้ให้เราจินตาการว่ามัน(เจ้าโปรแกรมที่เราจะสร้างขึ้น)มีลักษณะเหมือนกับวัตถุทั่วๆที่มีอยู่บนโลกนี้ เช่น รูปร่างหน้าตาของมันควรเป็นยังไง มันสามารถทำอะไรได้บ้าง ฯลฯ เมื่อคิดได้แล้วว่าวัตถุ(โปรแกรม)ของเรานั้นน่าจะคุณลักษณะอย่างไร ก็ให้จะนำคุณลักษณะเหล่านั้นมาร่างเป็นคลาส(class) ซึ่งคลาสเนี่ย ก็คือโค้ดที่ใช้อธิบายถึงกลุ่มของวัตถุที่มีคุณสมบัติเหมือนๆกัน โดยมีหน้าที่สำหรับใช้สร้างวัตถุขึ้นออกมาใช้งาน โดยวัตถุที่ถูกสร้างออกมาจากคลาสจะเรียกว่าอ็อบเจกต์(Object)

งานหลักๆของเราที่จะต้องทำเมื่อเราเขียนโปรแกรมด้วยรูปแบบ OOP ก็คือ การสร้างอ็อบเจกต์ออกมาใช้งาน ซึ่งการ”ใช้” ที่ว่าก็หมายถึงใช้คุณสมบัติ(properties) และความสามารถ(method) ที่มีอยู่ในอ็อบเจกต์ เพื่อทำงานบางอย่าง อ่า อย่างเพิ่งทำหน้างงนะ ทนอ่านต่อไปอีกนิดแล้วนาจะเข้าใจ

คุณสมบัติและความสามารถของอ็อบเจกต์ที่ผมพูดถึงนั้นจะถูกเขียนเป็นโค้ดอยู่ในภายคลาส ซึ่งเราจะนำคลาสไปใช้ในการสร้างอ็อบเจกต์ออกมาอีกทีหนึ่ง จำไว้ว่าอ็อบเจกต์จะต้องเกิดจากคลาสเสมอ นั้นหมายความว่าถ้าเราอยากได้อ็อบเจกต์ที่มีคุณสมบัติและความสามารถตามที่เราต้องการ เราก็ต้องเขียนโค้ดเพื่อสร้างคลาสของอ็อบเจกต์นั้นขึ้นมา ซึ่งโค้ดนั้นก็จะประกอบไปด้วยการกำหนดคุณสมบัติและความสามารถของอ็อบเจกต์ในคลาสนั้นๆ

แต่ข่าวดีก็คือ เราไม่จำเป็นต้องเสียเวลามานั่งสร้างคลาสขึ้นมาเองเสมอไป ถ้าอ็อบเจกต์ที่เราอยากได้นั้นมีความสามารถและคุณสมบัติเหมือนกับคลาสที่คนอื่นเขาสร้างเอาไว้เรียบร้อยแล้ว เพราะเราสามารถนำคลาสดังกล่าวมาสร้างอ็อบเจกต์แล้วนำอมันไปใช้ทำในสิ่งที่เราต้องการได้เลย ซึ่งบังเอิญเหลือเกินว่ามากกว่า 50% ของเนื้อหาในการเขียนโปรแกรมทั่วไปๆเราสามารถนำคลาสที่มีอยู่แล้วมาใช้สร้างอ็อบเจกต์ได้เลย เช่น เราต้องการใช้อ็อบเจกต์ที่มีความสามารถ”ปริ้น” งานออกมาจาก printer ก็สามารถสร้างอ็อบเจกต์ออกมาจากคลาส Printer(สมมตินะ) ที่มีอยู่แล้วได้เลย(มีอยู่แน่ๆ คลาส Printer เนี่ย) หรือถ้าเราต้องการอ็อบเจกต์ที่มีความสามารถในการควบคุมการทำงานของ Joystick ผ่านทางพอร์ต USB ก็เราก็สามารถหาคลาส Joystick ทีมีคนสร้างไว้แล้วมาใช้ได้เหมือนกัน หรืออีกตัวอย่างคือ อ็อบเจกต์ที่มีความสามารถ convert ไฟล์นามสกุลแบบ csv ให้กลายเป็นไหล์ xls เป็นต้น

การนำคลาสที่มีอยู่แล้วมาใช้สร้างอ็อบเจกต์(Code reused) นั้นถือเป็นหัวใจสำคัญข้อหนึ่งของการเขียนโปรแกรมแบบ OOP ทีเดียวครับ อย่าลืมว่าเจ้าอ็อบเจกต์ที่ถูกสร้างออกมาก็จะมีคุณสมบัติและความสามารถตามที่ระบุเอาไว้ในคลาสนะครับ สบายแฮ ละครับทีนี้


Class และ Object

เราสามารถสร้างคลาสขึ้นมาด้วยการใช้คีย์เวิร์ด class ตามด้วยชื่อคลาส และปิดท้ายคลาสด้วย end ดังตัวอย่างต่อไปนี้ครับ

cell_phone1.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CellPhone
def initialize(id, mfg, model)
@id, @mfg, @model = id, mfg, model
end

def call(number)
"Calling to #{number}"
end

def answer
"talking..."
end

def to_s
"number: #@id\nmfg: #@mfg\nmodel: #@model\n"
end
end

เรามาดูรายละเอียดของคลาสนี้ไปพร้อมๆกันนะครับ

คลาสนี้มีชื่อว่าคลาส CellPhone ซึ่งแน่นอนว่าเป็นคลาสของโทรศัพท์มือถือ อ็อบเจกต์ที่จะถูกสร้างออกมาจากคลาสนี้ต้องเป็น “วัตถุ” ที่มีคุณสมบัติเหมือนมือถือแน่นอน ภายในตัวคลาสมีการระบุคุณสมบัติและความสามารถไว้เอาไว้แล้วครับ โดยคลาสนี้กำหนดให้อ็อบเจกต์ของมัน (ก็มือถือนั่นแหละ) มีคุณสมบัติ 3 ประการครับคือ หมายเลขเครื่อง, ยี่ห้อ และ รุ่น ซึ่งโดยทั่วไปแล้วคุณสมบัติของคลาสนั้นจะสามารถแทนได้ด้วยตัวแปร instance ซึ่งในที่นี้ได้แก่ตัวแปร @id, @mfg, @model

ส่วนความสามารถที่ถูกกำหนดเอาไว้ในคลาสนี้คือ ความสามารถในการโทรออก, รับสายและแสดงคุณสมบัติของเครื่องมือถือ ซึ่งจะแทนด้วยเมธอด(method) call, answer และ to_s ครับ จริงๆแล้วเราจะมองว่าเมธอดนั้นคือฟังชั่น(function) ที่ถูกเรียกใช้โดยอ็อบเจกต์ก็ไม่ถึงกับผิดเสียทีเดียวครับ เราจะเห็นว่าการทำงานของเมธอดทั้งสองก็ง่ายๆครับ คือมันจะคืนค่าออกมาเป็นข้อความเมื่อมันถูกเรียกใช้

การเขียนเมธอดทำได้โดยใช้คีย์เวิร์ด def แล้วตามด้วยชื่อของเมธอด จากนั้นปิดท้ายเนื้อหาของโค้ดภายในเมธอดด้วยคีย์เวิร์ด end ครับ สำหรับโค้ดภายในตัวเมธอดนั้น ถ้าไม่มีการกับหนดคีย์เวิร์ด return เอาไว้เพื่อบอกว่าจะมีการคืนค่าอะไรออกมาจากเมธอด ภาษา Ruby จะถือว่าผลลัพธ์ของการแปลโค้ดบรรทัดสุดท้ายในเมธอดคือค่าที่จะคืนออกมาจากเมธอดเองโดยอัตโนมัติ นั่นหมายความว่าทุกๆเมธอดจะต้องมีค่าที่ถูกคืนออกมาทั้งนั้น ถ้าหากค่าที่คืนออกมาจากเมธอดมีค่าเป็น ค่าว่างเปล่า(null) เราจะถือว่าเมธอดนั้นไม่มีการคืนค่าก็ได้ครับ ผมว่าวิธีนี้ก็เข้าท่าดีเพราะไม่ต้องมากำหนด void ให้กับเมธอดเพื่อบอกว่ามันเป็นเมธอดที่ไม่คืนค่าเหมือนกับในภาษา Java หรือ C#

หลังจากเขียนคลาสเสร็จแล้วผมจะนำมันมาสร้างเป็นอ็อบเจกต์โดยให้ชื่อว่า my_phone ดังโค้ดตัวอย่างต่อไปนี้ จากนั้นเราจะลองรันซอร์ดโค้ดของโปรแกรมนี้ดูครับ (ถ้าใช้ SciTE ก็ให้ save ไฟล์ source code เป็นไฟล์ชื่อ cell_phone.rb ก่อนแล้วกด F5 เพื่อรันโปรแกรมได้เลย)

cell_phone2.rb
1
2
3
4
5
6
7
8
9
10
class CellPhone
# ไส้ในเหมือนกับโค้ดตัวอย่างข้างบนครับ
# …
End

my_phone = CellPhone.new("0891134103", "Nokia", "3310")
puts my_phone.to_s
puts my_phone.call(1175)
puts my_phone.answer


ผลลัพธ์ของการรันไฟล์ซอร์ซโค้ด cell_phone2.rb จะแสดงเป็นข้อความออกมาทางหน้าจอดังนี้
number: 0891134103
mfg: Nokia
model: 3310
Calling to 1175
talking...

ผมขอสรุปใจความสำคัญของโค้ด cell_phone2.rb ออกมาเป็นข้อๆดังนี้ครับ
1) การสร้างอ็อบเจกต์ออกมาจากคลาสใดๆทำได้โดยการเขียนชื่อคลาสนั้นๆแล้วตามด้วย “.new” ซึ่งหมายถึงการเรียกเมธอดที่ชื่อ new ซึ่งเป็นเมธอดพิเศษออกมาทำงาน ซึ่งค่าที่คืนออกมาจากเมธอด new ก็คืออ็อบเจกต์ของคลาสนั้นๆ ในที่นี้อ็อบเจกต์ที่ถูกสร้างออกมาจาก คลาส CellPhone จะถูกนำไปเก็บอยู่ในตัวแปร my_phone เพื่อใช้อ้างอิงในโอกาสต่อๆไป
2) ในบรรทัดที่ 6-8 จะแสดงการ “เรียกใช้เมธอด(calling method)”ต่างๆของอ็อบเจกต์ my_phone ออกมาใช้งาน ซึ่งการเรียกใช้เมธอดนั่นทำได้โดยการเขียนเครื่องหมายจุดแล้วตามด้วยชื่อของเมธอดเอาไว้ที่ด้านหลังของอ็อบเจกต์ my_phone จากตัวอย่างนั้นเราเรียกใช้เมธอด call, answer และ to_s ออกมาใช้ทั้งหมด
3) การทำงานของเมธอดทั้งสามนั้นทำเหมือนกันหมดครับ คือคืนค่าที่เป็นข้อความออกมาหลังจากที่เมธอดถูกเรียกใช้ จะต่างกันก็เพียงเนื้อหาของข้อความเท่านั้น ดังนั้นในบรรทัดที่ 6-8 เราจึงสั่งให้แสดงผลข้อความนั้นๆออกมาทางหน้าจอด้วยคำสั่ง puts
4) ตอนที่เราจะสร้างอ็อบเจกต์ด้วยการเรียกเมธอด new นั้นเราสามารถกำหนดคุณลักษณะ(properties) ให้กับอ็อบเจกต์ของเราได้โดยการใส่ค่า arguments เข้าไป ซึ่งจากตัวอย่าง เราใส่ค่าเข้าไปสามค่าคือ เบอร์โทร, ยี่ห้อโทรศัพท์, รุ่นของโทรศัพท์ ค่าเหล่านี้จะถูกส่งไปเป็น input ของเมธอด initialize อีกทีหนึ่ง ดังนั้นเราจะเห็นว่าเมธอด initialize และ เมธอด new นั้นมีความสัมพันธ์กันโดยตรงครับ สรุปว่าเมธอด new เมื่อถูกเรียกจะทำหน้าที่สร้างอ็อบเจกต์ แล้วก็จะทำหน้าที่รับค่าจากภายนอกด้วยถ้าเกิดมีการเขียนรายละเอียดเอาไว้ในเมธอด initialize ของคลาส ซึ่งเมื่อเมธอด new รับค่าเข้ามามันก็จะโยนค่าดังกล่าวไปให้เมธอด initialize ทันทีเพื่อให้เมธอด initialize นำไปประมวลผลต่อไป

โอเคนะครับ ถึงตรงนี้เราก็พอจะร็แล้วว่าคลาส, อ็อบเจกต์, เมธอดและตัวแปรของเรามีความสัมพันธ์กันอยู่ เรานำคลาสมาสร้างอ็อบเจกต์ ซึ่งภายในคลาสนั้นจะมีโค้ดที่เขียนไว้เพื่อกำหนด method และ properties สำหรับอ็อบเจกต์ของคลาสนั้นๆ เมื่อเราสร้างอ็อบเจกต์ออกมาจากคลาสด้วยการ “นิว(.new)” แล้ว เวลาเราจะเอาอ็อบเจกต์นั้นไปใช้เราก็แค่เรียกเมธอดของมันออกมาด้วยการใส่เครื่องหมายจุดแล้วตามด้วยชื่อเมธอดครับ

การสืบทอดคุณสมบัติของคลาส(Class Inheritance)

ทีนี้ผมขอย้อนกลับไปที่ผมเคยบอกว่า เราอาจไม่จำเป็นต้องมานั่งสร้างคลาสขึ้นมาใหม่ตั้งแต่ต้น เพราะอ็อบเจกต์ส่วนใหญ่ที่มีความสามารถแบบเดียวกับที่เราต้องการใช้นั้นมันมีคนไปสร้างคลาสเอาไว้เรียบร้อยแล้ว ซึ่งเราก็สามารถนำคลาสของเขามาใช้ได้เลย ไม่ต้องสร้างคลาสเองตั้งแต่ต้น
แต่มันอาจจะไม่ง่ายอย่างนั้นเสมอไปครับ
เพราะในความเป็นจริงนั้นไม่มีความต้องการของใครที่มันจะเหมือนกันไปหมดทุกอย่างครับ คลาสที่คนอื่นสร้างขึ้นมาอาจมีความสามารถที่เราต้องการอยู่ 8 อย่างแต่กลับขาดความสามารถอื่นไป 2 อย่างก็ได้ แล้วเราจะทำอย่างไร? จะสร้างใหม่ก็ใช่ทีเพราะเสียเวลามาก จะเอาของเขาเดิมๆมาใช้เลยก็ไม่ได้เพราะทำงานอย่างที่ต้องการได้ไม่ครบแน่ๆ แล้วจะทำไงดีอะ
ปัญหานี้มีทางออกครับ เพราะการเขียนโปรแกรมแบบ OOP มีคุณสมบัติที่เรียกว่า การสืบทอดคลาส(Class Inheritance) คุณสมบัตินี้จะทำให้เราสามารถนำคุณสมบัติจากคลาสหนึ่งมาใช้ในอีกคลาสหนึ่งได้ด้วยการถ่ายทอดคุณสมบัติให้แก่กัน

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

เพื่อความเข้าใจมากขึ้น ลองมาดูพิจารณาคลาสโทรศัพท์มือถือของเรากันอีกครั้งครับ
สมมติว่าเราต้องการสร้างอ็อบเจกต์มือถือที่มีความสามารถใช้งานบริการข้อมูลแบบ 3G ด้วยเราจะไม่มาสร้างคลาสมือถือใหม่ตั้งแต่แรก เพราะเรารู้แล้วว่าเราสามารถประหยัดเวลาด้วยการสืบทอดคุณสมบัติได้
ดังนั้นเราก็แค่สร้างคลาสโทรศัพท์มือถือใหม่ขึ้นมา โดยสมมติว่าชื่อคลาส CellPhone3G จากนั้นเราก็จะสืบทอดคุณสมบัติของความเป็นมือถือธรรมดาๆจากคลาส CellPhone โดยคุณสมบัติใหม่ที่เราจะเพิ่มเข้าไปในคลาส CellPhone3G ก็คือความสามารถในการส่ง MMS และความสามารถในการใช้ wifi ซึ่งก็คือการเขียนเมธอด mms และ wifi เพิ่มเข้าไปนั่นเอง

เราจะเรียกคลาส CellPhone ว่าคลาสแม่ (เป็นแม่แบบให้เขามาสืบทอดคุณสมบัติ) และเรียกคลาส CellPhone3G ว่าคลาสลูก (ไปรับถ่ายทอดคุณสมบัติมาจากคลาสแม่)

คลาส CellPhone3G จึงมีหน้าตาดังนี้ครับ

cell_phone_3g.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class CellPhone3G < CellPhone
def mms
"Send MMS"
end
def wifi
"Connect to Wifi network"
end
end

phone_3g = CellPhone3G.new("0814445555", "Apple", "iPhone")
puts phone_3g.to_s
puts phone_3g.mms
puts phone_3g.wifi

ซึ่งเราจะได้ผลลัพธ์จากการรันโค้ด cell_phone_3g.rb ดังนี้
number: 0814445555
mfg: Apple
model: iPhone
Send MMS
Connect to Wifi network

จากตัวอย่างในโค้ด cell_phone_3g.rb จะเห็นว่าในบรรทัดที่ 1 เราใช้เครื่องหมาย “<” แทนความหมายของการสืบทอดคุณสมบัติของคลาส โดยจะเขียนเครื่องหมาย “<” ไว้หลังคลาสลูกแล้วตามด้วยชื่อของคลาสแม่ที่ไปสืบทอดมา

เมื่อคลาสลูกสืบทอดคุณสมบัติมาจากคลาสแม่เรียบร้อยแล้ว อ็อบเจกต์ที่เกิดจากคลาสลูกนั้นก็จะสามารถเรียกใช้เมธอดหรือ properties ที่อยู่ในคลาสแม่ได้ด้วย ดังนั้นอ็อบเจกต์ cellphone_3g จึงสามารถเรียกใช้เมธอด to_s ได้เพราะมีเมธอดนี้อยู่ในคลาสแม่แล้ว

ทีนี้เราพอที่จะเห็นประโยขน์ของการเขียนโปรแกรมแบบ OOP อย่างชัดเจนขึ้นมาแล้วสิครับ ลองนึกดูว่าเราจะดีรับความสะดวกแค่ไหนถ้ามีคนสร้างคลาสที่เราต้องการไว้เรียบร้อยแล้ว หากเราต้องการคุณสมบัติบางอย่างของคลาสไหนเราก็ไปสืบทอดคลาสนั้นแล้วใส่เมธอดหรือพร็อพเพอร์ตี้ที่เราต้องการเพิ่มเติมลงไปในคลาส จากนั้นก็ใช้คลาสดังกล่าวสร้างอ็อบเจกต์ของเราออกมาใช้งาน ซึ่งการสืบทอดคุณสมบัติของคลาสจะช่วยลดเวลาในการพัฒนาโปรแกรมลงไปได้มาก เพราะเราไม่จำเป็นต้องเขียนโค้ดขึ้นมาใหม่ทั้งหมดอีกต่อไป
 

ไม่มีความคิดเห็น:

แสดงความคิดเห็น