หน้าเว็บ

วันศุกร์ที่ 28 สิงหาคม พ.ศ. 2552

Constructor และคุณสมบัติภายในคลาส

ลังจากที่เราทำความเข้าใจกับหลักการเบื้องต้นของ OOP เกี่ยวกับคลาสและอ็อบเจกต์แล้ว
วันนี้เราจะมาว่าถึงรายละเอียดที่อยู่ภายในคลาสกัน ซึ่งได้แก่ constrcutor, ตัวแปร instant, properties ของคลาส, ตัวแปรคลาส ครับ

Constructor
คอนสตรักเตอร์ก็คือเมธอดดีๆนี่เองครับ แต่ความพิเศษของมันอยู่ที่มันจะถูกเรียกใช้หลังจากที่มีการสร้างอ็อบเจกต์เสมอ โดยในภาษา Ruby จะกำหนดให้ชื่อของเมธอด constructor นั้นมีชื่อว่า "initialize" เสมอ ดังนั้นจากนี้ไปผมขอเรียกเมธอด constructor ว่า เมธอด initialize นะครับ

note: เมธอด Constrcutor ของภาษา Java หรือ C# คือเมธอดที่มีชื่อเดียวกับคลาสครับแต่หลักการทำงานนั้นเหมือนกันคือจะถูกเรียกใช้ตอนสร้างอ็อบเจกต์เสมอ)

เรายังจำกันได้นะครับว่าเวลาสร้างอ็อบเจกต์เราจะต้องเรียกใช้เมธอด "new" เช่นเขียนว่า iphone = Phone.new ก็คือการสร้างอ็อบเจกต์จากคลาส Phone โดยอ็อบเจกต์นั้นจะเก็บอยู่ในตัวแปร iphone ซึ่งเจ้าเมธอด new นี่แหละจะมีความสัมพันธ์โดยตรงกับเมธอด intiailize ของเรา นั่นคือหลังจากที่เราเรียกเมธอด new ตัวแปลภาษา Ruby จะเข้าไปหาว่าภายในคลาสนั้นมีการกำหนดเมธอดชื่อ initialize เอาไว้หรือไม่ ถ้ามี Ruby ก็จะเรียกเมธอด initialize ขึ้นมาใช้ทันทีครับ

ดังนั้นทุกๆครั้งที่เราสร้างอ็อบเจกต์ขึ้นมาจากคลาส เมธอด initialize ก็จะถูกเรียกใช้ก่อนเสมอ ซึ่งเมธอด initialize จะทำหน้าที่กำหนดสถานะเบื้องต้นของอ็อบเจกต์ ว่า

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

เราลองว่าดูการทำงานของเมธอด initialize และ ตัวแปร instant ไปพร้อมๆกันดีกว่าครับ
สมมติว่าผมมีคลาสที่ชื่อว่า Phone ดังตัวอย่างต่อไปนี้
class Phone
  def initialize(mfg, model, color)
    @mfg, @model, @color = mfg, model, color
  end

  def info
    "MFG: #{@mfg}\tMODEL:#{@model}\tCOLOR:#{@color}"
  end

  def call(number)
    puts "Calling #{number}"
  end
end
 
# create phone objects
phone_1 = Phone.new("Nokia", "3310", "Dark Blue")
phone_2 = Phone.new("Nokia", "N70", "White")
phone_3 = Phone.new("HTC", "Touch", "Black")
 
# call method info for each phones
puts phone_1.info
puts phone_2.info
puts phone_3.info

คลาส Phone ของเรามีเมธอดอยู่ 3 เมธอดครับคือ initialize, info และ call อย่างที่อธิบายไปแล้วว่าเมธอด initialize จะถูกเรียกใช้เสมอเมื่อมีการสร้างอ็อบเจกต์ด้วย new

สังเกตุนิดนึงนะครับว่าเมธอด initialize ในบรรทัดที่ 2 นั้นมีการกำหนดให้ต้องใส่พารามิเตอร์ลงไปด้วยกัน 3 ตัว ซึ่งค่าของพารามิเตอร์แต่ละตัวก็จะถูกกำหนดลงไปในตัวแปร instant ที่ชื่อ @mfg, @model และ @color ตามลำดับ เมื่อเมธอด initialize ถูกกำหนดให้ต้องใส่ค่าพารามิเตอร์ดังนั้นเวลาเราเรียกเมธอด “new” เราก็ต้องใส่พารามิเตอร์แบบเดียวกับที่กำหนดไว้ในเมธอด initialize ให้มันด้วย เพราะเมธอดทั้งสองต่างก็มีความสัมพันธ์ต่อกัน โดยค่าของพารามิเตอร์ที่ใส่เข้าไปในเมธอด initialize จะเป็นตัวกำหนดคุณลักษณะของอ็อบเจกต์แต่ละตัวด้วย

เมื่อสร้างคลาสเสร็จเรียบร้อยแล้วเราก็จะนำคลาสมาสร้างอ็อบเจกต์ดังแสดงในบรรทัดที่ 13-15 ซึ่งเป็นการสร้างอ็อบเจกต์ขึ้นมา 3 อ็อบเจกต์ด้วยการเรียกเมธอด new
อ็อบเจกต์แต่ละตัวจะมีคุณลักษณะที่แตกต่างกันไปตามค่าของพารามิเตอร์เราใส่ลงไปในเมธอด new เช่น ยี่ห้อ(@mfg), รุ่น(@model) และ สี(@color) ของอ็อบเจกต์โทรศัพธ์มือถือ ซึ่งค่าเหล่านี้จะถูกนำไปใช้สำหรับกำหนดค่าของตัวแปร instant ในเมธอด initialize อีกทีหนึ่งครับ ดังนั้นอ็อบเจกต์ที่เกิดจากคลาสเดียวกันก็อาจจะมีหน้าตาที่ต่างกันได้


Properties ของอ็อบเจกต์มือถือแต่ละเครื่อง (@mfg, @model, @color) ทำให้อ็อบเจกต์ที่มาจากคลาสมือถือเดียวกันมีหน้าตาต่างกัน แต่ความสามารถในการทำงาน (method) ยังเหมือนกันทุกประการซึ่งในที่นี้อ็อบเจกต์ทั้งสามทำได้แค่โทรออกและแสดงข้อมูลของเครื่องเท่านั้น (เมธอด call และ เมธอด info)

เมื่อเรารันซอร์ซโค้ด phone.rb เราจะได้ข้อความที่แสดงข้อมูลของอ็อบเจกต์โทรศัพท์มือถือแต่ละเครื่องดังนั้น ซึ่งเป็นผลลัพธ์มาจากโค้ดในบรรทัดที่ 17-19 ครับ
MFG: Nokia MODEL:3310 COLOR:Dark Blue
MFG: Nokia MODEL:N70 COLOR:White
MFG: HTC MODEL:Touch COLOR:Black

ถึงแม้ว่าอ็อบเจกต์ที่เกิดจากคลาสเดียวกันจะสามารถมีลักษณะที่ต่างกันไป แต่มันยังคงความสามารถในการทำงานได้เหมือนกันตามที่กำหนดไว้ในคลาส หรือพูดอีกอย่างก็คืออ็อบเจกต์พวกนี้มี properties ต่างกันได้แต่เมธอดที่กำหนดการกระทำนั้นยังคงเหมือนกัน ในที่นี้ทั้งอ็อบเจกต์ของ HTC touch, N70 และ 3310 ต่างก็ทำได้แค่โทรออก(เมธอด call) และเรียกดูรายละเอียด(เมธอด info) เหมือนกัน เพราะในคลาส Phone กำหนดเมธอดไว้แค่นั้น

ค่าทั่วไปของอ็อบเจกต์
คราวนี้ลองมาดูกันว่าถ้าผมเรียกเมธอด new แล้วไม่ใส่พารามิเตอร์จะเกิดอะไรขึ้น
สมมติว่าผมสร้างอ็อบเจกต์ phone_4 เพิ่มเข้าไปแบบนี้
class Phone #คลาสเหมือนเดิมจากตัวอย่างที่แล้ว # … end phone_4 = Phone.new puts phone_4.info
เมื่อลองรันซอร์ซโค้ดแล้วปรากฏว่าเกิดข้อผิดพลาดดังนี้ครับ

phone_2.rb:10:in `initialize': wrong number of arguments (0 for 3) (ArgumentError)
from phone_2.rb:10:in `new'
from phone_2.rb:10

อ่า... มันฟ้องว่าเราต้องใส่พารามิเตอร์ 3 ตัวให้กับเมธอด new ซึ่งก็เป็นอย่างที่เราคาดเอาไว้ครับ
อย่างไรก็ตามหากเราจะสร้างอ็อบเจกต์ออกมาใช่ เราไม่จำเป็นต้องมานั่งใส่ค่าพารามิเตอร์ให้ครบทุกครั้งเสมอไป เพราะเราสามารถบอกคลาสให้ตั้งค่าทั่วไป(Default) ให้กับอ็อบเจกต์ที่เราสร้างขึ้นมาได้เลย หมายความว่าถ้าเราไม่ใส่ค่าพารามิเตอร์เลยตอนที่เรา new อ็อบเจกต์ ค่าของพารามิเตอร์เหล่านั้นจะถูกกำหนดให้เป็นค่า default โดยอัตโนมัติครับ
class Phone
  def initialize(mfg="xxx", model="xxx", color="xxx")
    @mfg, @model, @color = mfg, model, color
  End
   # …
end
 
phone_1 = Phone.new
phone_2 = Phone.new("Nokia")
phone_3 = Phone.new("HTC", "Touch", "Black")
 
puts phone_1.info
puts phone_2.info
puts phone_3.info

phone_3.rb คือโค้ดที่ทำการปรับปรุงคลาส Phone ใหม่ โดยจะเห็นว่ามีการกำหนดค่า default ให้กับพารามิเตอร์แต่ละตัวในบรรทัดที่ 2
เมื่อเรา new อ็อบเจกต์โดยไม่ใส่พารามิเตอร์ ค่าพารามิเตอร์ของเมธอด initialize (mfg, model และ color) จะถูกกำหนดให้เป็นค่า default ซึ่งก็คือ “xxx” โดยอัตโนมัติ
มีข้อสังเกตุเล็กน้อยแต่ถ้าสมมติว่าเราใส่ค่าพารามิเตอร์เพียงตัวเดียวลงไป ค่าของตัวแปร instant ในเมธอด initialize ก็จะถูกกำหนดเพียงค่าเดียว(@mfg) ในขณะที่ตัวแปร instant ตัวอื่นก็จะถูกกำหนดให้เป็นค่า default เหมือนเดิม ดังแสดงในบรรทัดที่ 10-12 ครับ

ดังนั้นผลลัพธ์ของการรันซอร์ซโค้ด phone_3.rb จึงมีหน้าตาดังนี้
MFG: xxx MODEL:xxx COLOR:xxx
MFG: Nokia MODEL:xxx COLOR:xxx
MFG: HTC MODEL:Touch COLOR:Black

นำ Properties ของอ็อบเจกต์มาใช้ประโยชน์
จากตัวอย่างที่ผ่านมา เราเห็นแล้วว่าอ็อบเจกต์ที่เกิดจากคลาสเดียวกันนั้นสามารถมีคุณลักษณะ(properties หรือ attribute) ที่แตกต่างกันไปตามแต่เราจะกำหนด ทีนี้เราจะมาดูกันต่อครับว่า เราจะสามารถนำค่าของ properties ที่เรากำหนดให้อ็อบเจกต์แต่ละตัวใช้ประโยชน์ได้อย่างไร

โดยทั่วไปแล้วการใช้ประโยชน์จาก properties มีอยู่ด้วยกัน 2 ลักษณะคือ 1) อ่านค่าของ properties ตัวนั้นแล้วนำไปใช้ 2) กำหนดค่าเก็บไว้ใน properties เพื่อนำไปใช้ประโยชน์ในคราวต่อไป ซึ่งทั้งสองลักษณะนี้ก็คือการอ่านและเขียนค่าลงใน properties นั้นเอง
การทำให้ properties ของอ็อบเจกต์สามารถ “อ่าน” และ “เขียน” ได้นั้นก็ไม่ยากครับ เราเพียงแค่สร้างเมธอดที่มีชื่อเหมือนกับ properties ขึ้นมาเพื่อใช้เป็นช่องทางในการเข้าถึงค่าของ properties นั้น
class Phone
  def initialize(mfg="Nokia", model="5310", color="Black-Red")
    @mfg, @model, @color = mfg, model, color
  end
  def info
    "MFG: #{@mfg}\tMODEL:#{@model}\tCOLOR:#{@color}"
  end
  def color
    @color
  end
  def color=(new_color)
    @color = new_color
  end
end

phone_1 = Phone.new
puts "Default color is " + phone_1.color

phone_1.color = "White-Blue"
puts "Now, the color change to " + phone_1.color
puts phone_1.info

จากคลาส Phone ในโค้ด phone_4.rb นั้น ผมกำหนดให้เราสามารถตรวจสอบสีของอ็อบเจกต์มือถือ และเปลี่ยนสีของอ็อบเจกต์มือถือได้ตามต้องการ

จะพบว่ามีเมธอดชื่อ color และ color= เพิ่มเข้ามา โดยเมธอด color นั้นจะทำหน้าที่คืนค่าของสีที่อยู่ในตัวแปร @color ออกมาซึ่งก็คือการ “อ่าน” ค่าสีออกมาจาก properties ที่ชื่อ color ในขณะที่เมธอด color= จะทำหน้าที่กำหนดค่าสีใหม่ให้กับตัวแปร @color ซึ่งก็เปรียบเสมือนการ “เขียน” ค่าสีลงไปใน properties ที่ชื่อ color ด้วยเช่นกัน เรามักจะเรียกเมธอดที่ทำหน้าที่ “อ่าน” ค่าของ properties อย่างเมธอด color ว่าเป็นเมธอด “getter” และเรียกเมธอดที่ทำหน้าที่ “เขียน” เหมือนอย่างเมธอด color= ว่าเมธอด “setter” ครับ

เมื่อเรารันรันโค้ด phone_4.rb ก็จะได้ผลลัพธ์ดังนี้ ซึ่งจะเห็นว่าการอ่านและเขียนค่าของ properties color ในบรรทัดที่ 17 และ19 นั้นจะทำผ่านเมธอดอย่าง color และ color= ทั้งสิ้น
Default color is Black-Red
Now, the color change to White-Blue
MFG: Nokia MODEL:5310 COLOR:White-Blue


เราสามารถนำเทคนิคการสร้างเมธอด setter และ getter ไปใช้กับ properties ตัวอื่นๆของคลาสได้ด้วยครับ ซึ่งผมสามารถปรับปรับคลาส Phone ออกมาใหม่ได้ดังโค้ด phone_5.rb
class Phone
  def initialize(mfg="Nokia", model="5310", color="Black-Red")
    @mfg, @model, @color = mfg, model, color
  end
  def info
    "MFG: #{@mfg}\tMODEL:#{@model}\tCOLOR:#{@color}"
  end
  def color
    @color
  end
  def color=(new_color)
    @color = new_color
  end
  def mfg
    @mfg
  end
  def mfg=(new_mfg)
    @mfg = new_mfg
  end
  def model
    @model
  end
  def model=(new_model)
    @model = new_model
  end
end
 
phone_1 = Phone.new
puts phone_1.info
 
phone_1.mfg = "Samsung"
phone_1.model = "Omnia"
phone_1.color = "Black"
phone_1.info
puts phone_1.info

จากโค้ด phone_5.rb properties ทุกตัวทั้ง mfg, model และ color นั้นต่างก็สามารถอ่านและเขียนผ่านทางเมธอดได้แล้ว ซึ่งหลังจากการทดลองรันโค้ด phone_5.rb จะพบว่าค่าของ properties ทุกตัวนั้นจะเปลี่ยนไปจากค่า default ในตอนแรกเมื่อเรากำหนดค่าของ properties แต่ละตัวใหม่ในบรรทัดที่ 31-33 ดังนั้นเมื่อเรียกดูสถานะของ properties ด้วยเมธอด info ก็จะได้ผลลัพธ์ดังนี้

MFG: Nokia MODEL:5310 COLOR:Black-Red
MFG: Samsung MODEL:Omnia COLOR:Black

การเขียนเมธอด setter และ getter ในโค้ด phone_5.rb นั้นออกจะยาวเกินไปครับสำหรับค่า properties เพียงแค่ 3 ตัวถ้ามี properties ซัก 10 ตัว คงต้องเขียนกันถึง 20 เมธอด คงดูไม่ดีแน่ ดังนั้น Ruby จึงมีวิธีประกาศเมธอด setter และ getter แบบสั้นๆครับ ซึ่งเราเรียกว่าการใช้ attribute accessor

Note: ในที่นี้ Properties และ Attributes คือสิ่งเดียวกันซึ่งหมายถึงคุณลักษณะของอ็อบเจกต์นะครับ แต่บางทีอาจจะเรียกต่างกันไปบ้างเท่านั้น

ดังนั้นผมจึงสามารถเขียนนำโค้ด phone_5.rb มาเขียนใหม่โดยใช้ attribute accessor เพื่อลดจำนวนโค้ดได้ดังนี้
class Phone
  attr_accessor :mfg, :model, :color
  def initialize(mfg="Nokia", model="5310", color="Black-Red")
    @mfg, @model, @color = mfg, model, color
  end
  def info
    "MFG: #{@mfg}\tMODEL:#{@model}\tCOLOR:#{@color}"
  end
end
 
phone_1 = Phone.new
puts phone_1.info
 
phone_1.mfg = "Samsung"
phone_1.model = "Omnia"
phone_1.color = "Black"
phone_1.info
puts phone_1.info

ในบรรทัดที่ 2 ของโค้ด phone_6.rb นั้นมีการประกาศ attribute accessor ให้กับ properties โดยใช้คีย์เวิร์ด attr_accessor ทำให้ properties ทั้งสามตัวของคลาสนี้มีคุณสมบัติเป็นเมธอด setter และ getter โดยอัตโนมัติ ซึ่งจะเห็นได้ว่าการใช้ attribute accessor สามารถลดจำนวนของโค้ดที่ต้องเขียนได้มากทีเดียว
ดังนั้นผลลัพธ์ของการรันโค้ด phone_6.rb จึงเหมือนกับผลลัพธ์ของการรันโค้ด phone_5.rb ทุกประการ

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

วันศุกร์ที่ 21 สิงหาคม พ.ศ. 2552

Irb (Interactive Ruby) เครื่องมือที่คนเขียน Ruby ทุกคนต้องทำความรู้จัก

Irb หรือ Interactive Ruby เป็นโปรแกรมที่มีมาให้พร้อมกับตัวติดตั้งภาษา Ruby ตั้งแต่แรก เราสามารถเขียนโค้ดภาษา Ruby ด้วย irb ซึ่งมันจะแปลโค้ดภาษา Ruby และแสดงผลลัพธ์พร้อมค่าที่ return ออกมาจากการแปลโค้ดให้ทุกครั้ง ซึ่งการที่มันแสดงค่า return ของการแปลโค้ดแต่ละครั้งนั้น มีประโยชน์มากในการ debug เพราะมันช่วยให้เราเข้าใจที่มาที่ไปและการทำงานของโค้ดโดยละเอียด

สำหรับท่านที่ใช้ Ruby บน Window นั้นสามารถเรียกใช้ irb ได้โดยการพิมพ์ “irb” ลงไปที่ Windows command prompt ดังตัวอย่างด้านล่าง



จากรูปจะเห็นว่า Windows Command prompt จะเปลี่ยนเป็น irb prompt แสดงว่า irb พร้อมที่จะทำงานแล้ว
เมื่อผมลองพิมพ์โค้ดโดยใช้คำสั่ง puts ลงไป สิ่งที่ได้ก็คือข้อความที่แสดงออกมาบนหน้าจอ และค่าผลลัพธ์ที่ return ออกมาหลังจากที่ Ruby แปลโค้ดบรรทัดนี้เสร็จ โดยจะใช้เครื่องหมาย “=>” เพื่อแสดงว่ามันคือค่า return จากตัวอย่างนี้ค่า return ก็คือ nil หรือค่าว่างเปล่านั้นเองครับ

ลองดูตัวอย่างการใช้ irb และค่าที่มัน return ออกมาในแต่ละครั้งกันดีกว่าครับ จะได้ get idea
C:\>irb
irb(main):001:0> 1+1
=> 2
irb(main):002:0> "one" + "two"
=> "onetwo"
irb(main):003:0> 3.times { puts "Help! " }
Help!
Help!
Help!
=> 3
irb(main):004:0> x = "Arsenal"
=> "Arsenal"
irb(main):005:0> x
=> "Arsenal"
irb(main):006:0> x.upcase
=> "ARSENAL"
irb(main):007:0> x
=> "Arsenal"
irb(main):008:0> x.reverse
=> "lanesrA"
irb(main):009:0>


สำหรับผมแล้ว irb เป็นเหมือนกับเป็นห้องทดลองเล็กๆที่ภาษา Ruby เตรียมไว้ให้ใช้ คืออยากรู้ว่าถ้าเขียนโค้ดแบบนั้นแบบนี้แล้วผลลัพธ์จะเป็นอย่างไรก็พึ่ง irb นี่แหละครับ
&nbps;

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

วันพฤหัสบดีที่ 6 สิงหาคม พ.ศ. 2552

ความเข้าใจพื้นฐานก่อนเริ่มเขียน Ruby ตอนที่ 2

6) Garbage Collector ใน Ruby
ตัวแปลภาษา Ruby มีฟังก์ชั่นที่เรียกว่า Garbage Collector ซึ่งทำหน้าที่บริหารจัดการหน่วยความจำโดยอัตโนมัติ เพื่อให้โปรแกรมสามารถใช้พื้นที่ในหน่วยความจำได้อย่างมีประสิทธิภาพมากที่สุด ซึ่ง Garbage Collector นั้นก็เป็นฟังก์ชั่นที่ใช้ในภาษา Java และ C# เช่นกัน

7) ไม่ต้องมีตัวจบบรรทัด
เนื่องจากภาษา Ruby แปลโค้ดแบบ interpreter แปลไปทีละบรรทัด เราเลยไม่ต้องใส่ตัวจบบรรทัดให้มัน ภาษา Ruby จะแยกแยะโค้ดแต่ละบรรทัดได้เองจากการขึ้นบรรทัดใหม่
อย่างไรก็ภาษา Ruby ก็ยังอนุญาตให้ใช้เครื่องหมาย ";"(comma) แทนการขึ้นบรรทัดใหม่ได้ในกรณีที่เราต้องการเขียนโค้ดหลายๆชุดไว้ในบรรทัดเดียว ดังตัวอย่างต่อไปนี้ครับ


a=10; b=15; sum=a+b; puts sum;


ซึ่งจะมีความหมายเดียวกับการที่เราเขียนแยกบรรทัด ดังตัวอย่างด้านล่างนี้ โดยการรันซอร์ซโค้ดทั้งสองแบบจะได้ผลลัพธ์เหมือนกันคือการพิมพ์ค่าของ sum ออกมาทางหน้าจอ

1
2
3
4
a = 10
b = 15
sum = a + b
puts sum


8) พิมพ์ข้อความออกมาทางหน้าจอ
เรามีวิธีสั่งให้ Ruby พิมพ์ข้อความออกมาทางหน้าจออยู่ 3 วิธีด้วยกันคือ

วิธีผลลัพธ์
ใช้คำสั่ง putsพิมพ์ข้อความออกทางหน้าจอพร้อมกับขึ้นบรรทัดใหม่ให้ด้วย
ใช้คำสั่ง printพิมพ์ข้อความออกทางหน้าจออย่างเดียวโดยไม่ขึ้นบรรทัดใหม่ให้
ใช้คำสั่ง printfพิมพ์ข้อความออกทางหน้าจออย่างเดียวโดยไม่ขึ้นบรรทัดใหม่ให้ และสามารถกำหนดลักษณะของการแสดงผลข้อความได้ด้วย


โค้ดต่อไปนี้จะเป็นตัวอย่างการพิมพ์ข้อความออกทางหน้าจอของทั้งสามรูปแบบครับ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# พิมพ์ข้อความพร้อมกับขึ้นบรรทัดใหม่ด้วย puts
puts "Arsenal"
 
# พิมพ์ข้อความอย่างเดียวไม่ต้องขึ้นบรรทัดใหม่ด้วย print
print "Manchester United"
 
# แต่ถ้าอยากขึ้นบรรทัดใหม่ด้วย print ก็ใส่สตริง "\n" ที่แทนการขึ้นบรรทัดใหม่เข้าไป
print "Chelsea\n"
 
# แสดงค่าของตัวแปรร่วมกับสตริงด้วย printf
frustrated = "Liverpool"
printf "%s : This is Anfield", frustrated
 
# ถ้าจะแสดงค่าของตัวแปรร่วมกับสตริงด้วย puts ก็ใส่ตัวแปรลงไปในเครื่องหมาย "#{ }"
puts "#{frustrated} : This is Anfield"


ซึ่งจะได้ผลลัพธ์ดังนี้
Arsenal
Manchester UnitedChelsea
Liverpool : This is AnfieldLiverpool : This is Anfield

หมายเหตุ: จริงๆแล้ว puts, print และ printf ไม่ใช้ "คำสั่ง" ครับแต่พวกมันเป็นเมธอด !? ซึ่งเมธอดที่เรียกใช้ได้โดยที่เราไม่ต้องสร้างอ็อบเจกต์ขึ้นมาเหล่าจะสังกัดอยู่ในคลาส NilClass ครับ ดังนั้นหากเราจะเรียกมันว่า "คำสั่ง" ก็คงไม่ผิดในแง่ของการนำมาใช้งานนัก

9) Parallel Assignment
ภาษา Ruby สามารถกำหนดค่าให้กับตัวแปรในแบบที่เรียกว่า parallel asignment ได้ครับ คือใช้เครื่องหมายเท่ากับแค่อันเดียวในการกำหนดค่าให้ตัวแปรหลายๆตัว ซึ่งการกำหนดค่าแบบนี้จะพบเห็นได้บ่อย ในภาษา Ruby ครับ

ลองมาดูกันสักหน่อยว่า parrelle asignment นั้นหน้าตาเป็นยังไง
สมมติว่าเราอยากจะกำหนดค่าให้กับตัวแปร 4 ตัว ตามปกติแล้วเราอาจจะทำได้ด้วยการเขียนโค้ด 4 บรรทัดแบบนี้

1
2
3
4
model = "Jazz",
mfg = "Honda"
color = "White"
cost = 640000


แต่ถ้ากำหมดค่าแบบ parallel asignment เราจะใช้แค่บรรทัดเดียว ซึ่งตะได้ผลลัพธ์เหมือนกับโค้ดด้านบน


model, mfg, color, cost = "Jazz", "Honda", "White", 640000


อย่างไรก็ตาม ไม่ได้หมายความว่าเราจำเป็นต้องใช้ parallel asignment ทุกครั้งไปนะครับ ทั้งนี้ขึ้นอยู่กับความเหมาะสมและความชอบของแต่ละคนครับ