หน้าเว็บ

วันศุกร์ที่ 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 กันต่ออีกนิดในบทหน้าครับ
 

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

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