All About OOP – ตอนที่ 1 มารู้จัก class และ object กันเถอะ

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

 

เปิดซีรีย์บทความใหม่ All About OOP … บทความชุดนี้จะพูดถึงแนวคิดการเขียนโปรแกรมในรูปแบบ Object Oriented Programming ซึ่งเป็นแนวคิดและสไตล์การเขียนโปรแกรมที่โปรแกรมเมอร์ (หรือ Developer) คงพบเจอกัน โปรเจคเกือบทุกโปรเจคที่ทำ หรือแม้แต่โค้ดที่โหลดมาใช้จากอินเตอร์เน็ทส่วนใหญ่มันจะอยู่ในรูปของ class และ object เสมอ

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

บทความชุดนี้จะพูดถึงอะไรบ้าง

ที่กะไว้คร่าวๆ คือจะเล่าเรื่อง class-object กันก่อน ตามด้วยคอนเซ็ปตัวหลักของ OOP อย่าง encapsulation, polymorphism, และ inheritance ก่อนจะปิดท้ายด้วย best practices ในการเขียนโค้ดตามแนวทางของ OOP โดยใช้หลักการต่างๆ เช่น Design Pattern หรือแนวคิดอย่าง S.O.L.I.D. Principle เป็นต้น และถ้ามีเวลาจะพูดถึงประวัติของ OOP ด้วยโดยจะไม่อิงกับภาษาใดภาษาหนึ่งเป็นหลักนะ syntax จะใช้แบบกลางๆ ที่ไม่ว่าจะเคยเขียนภาษาไหนมาก่อนก็น่าจะอ่านออกกัน (แต่ถ้าโค้ดชุดไหนอิงกับภาษาอะไรเป็นพิเศษจะคอมเมนท์เอาไว้ให้)

… แต่ก็ไม่รู้ว่าเขียนไปเรื่อยๆ จะแยกหัวข้อออกเป็นแบบไหนนะ ถ้าอยากติดตามว่าเขียนถึงไหนแล้วสามารถดูได้ที่สารบัญของซีรีย์นี้ข้างล่าง นี่ได้เลย

developer

บทความชุด: All About Object-Orient Programming

รวมบทความเจาะลึกเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ ตั้งแต่แนวคิด วิธีการใช้งาน ตัวอย่างและหลักการใช้ต่างๆ เช่น S.O.L.I.D หรือการใช้ Design Pattern

เกริ่นกันก่อนนะ

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

เช่นดูโค้ดชุดข้างล่าง

int x, y, z
print("please input number1: ")
x = scanInt()
print("please input number2: ")
y = scanInt()
z = x + y
print("answer is " + z)

โค้ดตัวอย่างนี้เป็นโปรแกรมง่ายๆ ที่รับค่า integer จากผู้ใช้มา 2 จำนวนแล้วหาผลบวกของตัวเลข 2 จำนวนนี้แล้วแสดงผลออกมา สไตล์การเขียนโค้ดแบบ Imperative Programming (หรือที่เรียกกันว่าการเขียนแบบ structural/procedural ถ้าอยากทราบความแตกต่าง อ่านเพิ่มเติมได้ที่ Programming paradigm – การเขียนโปรแกรมก็มี “กระบวนท่า (ทัศน์)” นะ) หรือการเขียนโปรแกรมแบบ how-to คืออยากให้โปรแกรมทำอะไรได้ เราก็สั่งไปเรื่อยๆ ไล่จากการขอตัวเลข 2 ตัวจากผู้ใช้ เอามาบวกกันแล้วก็ปริ๊นค่านั้นออกมา

สำหรับคนที่เพิ่งเริ่มเรียนรู้ OOP อย่างเช่นเราเอง … เนื่องจากเราเรียนกันมา เขาบอกกันมาว่าโค้ดแบบนี้มันไม่ดีนะ (ไม่ดียังไงก็ไม่รู้ ฮา) กลับมาอ่านใหม่แล้วไม่รู้เรื่อง เป็นโครงสร้างที่ไม่เหมาะสม พวกเราเลยเปลี่ยนโค้ดข้างบนให้อยู่ในรูปของคลาสซะ

class Calculator{
	
	int x, y, z

	function inputNumbersFromUser(){
		print("please input number1: ")
		x = scanInt()
		print("please input number2: ")
		y = scanInt()
	}
	
	function findAnswer(){
		z = x + y
	}
	
	function printAnswer(){
		print("answer is " + z)
	}
}

Calculator c = new Calculator()
c.inputNumber()
c.findAnswer()
c.printAnswer()

โค้ดหน้าตาประมาณนี้น่าจะเป็นสิ่งที่โปรแกรมเมอร์ สาย OOP มือใหม่มักจะเขียนกัน และเราก็เป็นหนึ่งในนั้นด้วย (ฮา) นั่นคือการตัดโค้ดที่เคยเขียนในรูปแบบแรกออกเป็นส่วนๆ แล้วยัดมันลงไปอยู่ใน method ของ class ที่สร้างขึ้นมาครอบ…

 

แล้วก็บอกว่าโค้ดที่เราเขียนขึ้นมาน่ะ เป็น OOP แล้วนะ!

 

น่าจะเคยเจอเหตุการณ์แบบนี้ใช่มั้ยล่ะ นั่นเพราะตอนเริ่มเขียนโปรแกรมมักจะติดแนวคิดของการเขียนแบบ Imperative มา พอเขียนคลาสได้ก็จับเอาการเขียนเก่าๆ มาเขียนใหม่ในรูปแบบขอ method แต่ก็ยังทำงานแบบ “บน-ลง-ล่าง” อยู่ดี

เราเขียนโค้ดในรูปแบบนี้โดยเชื่อว่ามันเป็น OOP มานานถึง 5 ปีก่อนจะเริ่มเข้าใจว่า Object-Oriented น่ะมันล้ำลึกกว่านั้นเยอะ

เอาล่ะ อ่านมาถึงตรงนี้อาจจะสงสัยว่า ถ้าแบบนั้นการเขียนโปรแกรมในรูปแบบของ OOP มันต้องออกมาหน้าตาแบบไหนน่ะ แต่ก่อนที่เราจะไปลงลึกกับเนื้อหาเชิงวัตถุ เรามาทบทวนสิ่งจำเป็นที่ต้องใช้นั่นคือ class และ object กันก่อนดีกว่านะ (แต่สำหรับคนที่ยังเขียน OOP ไม่เป็นแนะนำให้ไปอ่าน สรุป Advance Programming – OOP ก่อนก็ดีนะ)

class & object แม่พิมพ์และวัตถุ

ในการเขียนโปรแกรม การจัดการข้อมูลหรือ Data เป็นเรื่องสำคัญ แต่ก็ต้องไม่ลืมว่าคอมพิวเตอร์ทำงานด้วยหลักการทางคณิตศาสตร์ ดังนั้นชนิดของข้อมูลประเภทตัวเลขจึงเยอะเป็นพิเศษเช่น int long float double ต่อมาก็มีการสร้างตัวแรกประเภทตัวอักษรคือ char ตามมา ในภาษาสมัยใหม่มักจะมีชนิดตัวแปรที่เก็บค่าแค่ true/false หรือที่เรียกว่า boolean มาด้วย

ด้วยชนิดของข้อมูลพวกนี้ มนุษย์ก็สามารถทำการแทนค่า (data representation) ของในโลกจริงๆ ให้คอมพิวเตอร์รู้เรื่องได้แล้ว เช่น

– ต้องการเก็บข้อมูลของพนักงานก็ต้องสร้างอะไรประมาณนี้

int id
char[] first_name
char[] last_name
char[] position
char gender
float salary

สังเกตว่าเราต้องการสร้างตัวแปรมากมายตั้งแต่ int float char หลายตัวเลยเพื่อเก็บข้อมูลของพนักงานหนึ่งคน นั่นเป็นเพราะชนิดตัวแปร “พนักงาน” นั้นไม่มีกำหนดมาให้ในภาษาโปรแกรม นั่นทำให้เราต้องจำเอาเองว่าตัวแปร 6 ตัวนี้หมายถึงพนักงานหนึ่งคน

– หรือถ้าต้องการเก็บข้อมูลของ “วัน” เราก็อาจจะสร้างอะไรออกมาประมาณนี้

int date
int month
int year

ปัญหาตามมาคือถ้าเราเกิดอยากรู้ว่าวันที่อยู่ต่อจากวันนี้ไปอีก 10 วันคือวันที่เท่าไหร่ ก็ต้องเขียน if-else ที่เยอะมากมาย

นักวิทยาศาสตร์คอมพิวเตอร์ในสมัยก่อนเขาเจอปัญหาพวกนี้เข้าบ่อยๆ ก็เลยมีคนคิดคอนเซ็ปว่า

 

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

 

นั่นหมายความว่า เราสามารถสร้างของแบบนี้ได้

Employee emp1

หรือสร้างวันที่

Day d1
//ที่พอจะสั่งว่าขอวันอีก 10 วันต่อจากนี้ก็สั่งว่า
d1.next(10)

สังเกตว่าการเขียนแบบนี้เริ่มเป็นภาษาคนมากขึ้น มองปุ๊บก็รู้ว่า emp1 น่ะคือตัวแทนข้อมูลพนักงาน หรือวันที่ d1 ที่สั่งคำสั่ง .next(10) ก็พอจะเดาได้ง่ายกว่าการเขียนโค้ดเยอะๆ แบบเก่า

แต่ใช่ว่าอยู่ๆ เราจะสั่งให้คอมพิวเตอร์สร้างตัวแปร emp1 ขึ้นมาได้เลยโดยไม่บอกมันว่า Employee น่ะหน้าตาเป็นอย่างไร ใช่แล้ว คอมพิวเตอร์ไม่ได้ฉลาดขนาดนั้น แนวคิดของ class จึงเกิดขึ้นมาด้วยเหตุนี้

class: แม่พิมพ์ ที่เอาไปใช้งานไม่ได้

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

ตัวอย่างเช่น ถ้าเราต้องการบ้าน แล้วไปบอกวิศวกรว่า “สร้างบ้านให้หลังนึงสิ” วิศวกรก็จะทำหน้างง แล้วถามกลับว่า แล้วบ่านที่จะเอาน่ะ เป็นแบบไหนล่ะ

นั่นหมายความว่า เขาไม่รู้จักนิยามคำว่า “บ้าน” ของเรา แต่ถ้าเราไปหาเขาใหม่แล้วยื่นแบบแปลนบ้านหรือพิมพ์เขียวที่สถาปนิกวาดใส่กระดาษมาให้ อันนี้ค่อยคุยกันต่อได้หน่อย

แต่อย่างไรก็ตาม ถึงเราจะได้แบบแปลนของบ้านมาแล้ว มันก็เป็นแค่กระดาษ ยังใช้อยู่อาศัยไม่ได้จนกว่าเราจะเอาไปสร้าง “วัตถุ” ขึ้นมาจากแปลนนี้ นั่นคือที่มาของ object

object: วัตถุที่สร้างจากแม่พิมพ์ มีได้หลายชิ้น แต่ละชิ้นไม่ต้องเหมือนกัน

หมายถึง “วัตถุ” ที่สร้างขึ้นมาจากคลาสต้นแบบ ซึ่งสามารถเอาไปใช้งานได้จริงๆ

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

คลาสคือแปลนบ้าน แต่เราสามารถเอาแปลนบ้านนี้ไปสร้างบ้าน (วัตถุ) ได้หลายหลังเลย และแต่ละหลังก็ไม่ต้องเหมือนกัน บางหลังมีการทาสีเพิ่มเติม บางหลังมีการปลูกต้นไม้ไว้ บางหลังก็สร้างปล่องไฟด้วย แต่จะเห็นว่าโดยรวมแล้ว พวกมันคือ “บ้าน” ตามที่ได้สร้างคลาสเอาไว้

2 ส่วนประกอบหลักของ class

เรื่องต่อไปที่เราจะพูดถึงคือส่วนประกอบที่เราจะต้องเขียนใส่ลงไปในคลาส มี2อย่างหลักๆ นั่นคือ

  • properties – แทนสิ่งที่วัตถุจะ “มี”
  • action (หรือที่นิยมเรียกว่า method) – แทนสิ่งที่วัตถุ “ทำได้”

– properties: สิ่งที่วัตถุมี

ถึงแม้จะบอกว่าเราสร้างชนิดข้อมูลขึ้นมาใหม่ แต่ยังไงก็ตาม คอมพิวเตอร์ก็รู้จักชนิดข้อมูลพื้นฐาน ที่เราเรียกกันว่า primitive data type ไม่กี่ชนิดหรอก เช่น int  long  float  double  char  boolean หรืออย่างดีหน่อยถ้าภาษานั้นเก่งก็จะนับรวม string เข้าไปด้วย

ส่วนประกอบหลักอย่างแรกของคลาสนั้นก็คือการรวมพวกชนิดตัวแปรพวกนี้เข้าไว้ด้วยกัน (อาจจะเลือกใช้คลาสอื่นมาอีกทีก็ได้นะ)

ตัวอย่างเช่น

ถ้าเราสร้างคลาสมนุษย์ขึ้นมา โดยให้ชื่อว่าคลาส Human ละกันนะ

ความรู้เสริมนิดหน่อย การตั้งชื่อในภาษาโปรแกรมส่วนใหญ่ (บางภาษาก็ไม่ทำตาม) นิยมตั้งชื่อตัวแปรทุกอย่างในรูปของตัวอักษรภาษาอังกฤษตัวเล็ก ยกเว้นชื่อคลาสที่นิยมตั้งชื่อโดยให้ตัวอักษรตัวแรกเป็นตัวใหญ่ หากสนใจเรื่องนี้สามารถอ่านต่อได้ที่ camel case VS snake case .. สไตล์การเขียนโค้ด 2 แบบที่เจอบ่อย

ส่วนหน้าตาของโค้ดก็จะออกมาประมาณนี้

class Human {
	string name
	int age
	Job jobs[]
	char sex
}

หมายความว่าถ้าเราจะสร้าง Human ขึ้นมาหนึ่งคน ก็จะประกอบด้วย ชื่อ / อายุ / อาชีพ / และเพศ เจ้าพวกนี้แหละเราเรียกมันว่า properties คือสิ่งที่เมื่อเอาคลาสไปสร้างวัตถุขึ้นมาชิ้นหนึ่ง วัตถุชิ้นนั้นจะ “มี” ค่าตามนี้เก็บอยู่ภายในตัวมัน

– action (method): สิ่งที่วัตถุทำได้

ถ้าแค่ความสามารถในการรวมตัวแปรต่างๆ มากรุ๊ปไว้ด้วยกัน คงไม่ต่างกับ struct หรือ union ในภาษาซี สิ่งที่คลาสมีมากกว่านั้นคือมันสามารถยัด “การกระทำ” ลงไปได้ด้วย

และถ้ายังจำได้อยู่ ในภาษาโปรแกรมมิ่งมีอยู่อย่างเดียวที่มีความหมายเชิง action หรือการกระทำ นั่นคือ function

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

method คือ function ที่เป็นของ class

หน้าตาคลาสของเราเลยจะกลายเป็นแบบนี้

class Human {
	string name
	int age
	Job jobs[]
	char sex
	
	function walk(){
		//เดินไปเดินมา
	}
	
	function eat(){
		//กินๆๆๆ
	}
	
	function sleep(){
		//นอนแป๊ป
	}
}

หน้าตาก็ดูเหมือน function เลยเนอะ แต่สิ่งที่ทำให้ method เก่งกว่า function ก็คือมันสามารถทำงานร่วมกับ properties ของคลาสได้ โดยจะใช้คีย์เวิร์ด this หรือ self นำหน้า properties ที่ต้องการอ้างอิงถึง เช่น

function displayName(){
	print("my name is " + this.name)
}

หรือสำหรับบางภาษาก็อนุญาตให้เราละคีย์เวิร์ด this หรือ self ได้นะ ซึ่งไว้เราจะอธิบายในบทต่อๆ ไป

function displayName(){
	print("ฉันชื่อว่า" + name)
}

สรุปก็คือ method คือ action ของคลาส ทำงานเหมือนกับ function แต่สิ่งที่เก่งกว่าคือมันสามารถทำงานร่วมกับ properties ได้ด้วย

การสร้าง object จาก class และการใช้งานมัน

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

ตัวแปร = new ชื่อคลาส()

เดี๋ยวจะขอยกตัวอย่างการสร้าง object จากคลาส Human ขึ้นมาละกัน ให้ object ตัวนี้ชื่อว่า bob

สำหรับภาษาที่ซีเรียสเรื่องชนิดตัวแปร (type-sensitive) ตัวอย่างเช่น

//Java, C#
Human bob = new Human();

แต่สำหรับภาษาที่ไม่ซีเรียสเรื่องชนิดตัวแปร (type-insensitive) จะออกมาหน้าตาประมาณ

//PHP
$bob = new Human();

//Swift
var bob = Human()

//Python
bob = Human()

//เขียนสั้นลงเรื่อยๆ เนอะ?

ไม่ว่าเราจะสร้าง object ขึ้นมาด้วยภาษาอะไร แต่ประเด็นหลักคือ object ตัวนี้จะทำได้ทุกอย่างตามที่คลาสกำหนดไว้

นั่นแปลว่า bob ของเราสามารถเรียกใช้ทั้ง properties และ method ที่ประกาศไว้ได้ผ่านการใช้สัญลักษณ์  (dot) หรือในบางภาษาจะเป็น  ->  (arrow)

bob = new Human()
bob.name = "บ๊อบ"
bob.displayName() //output: ฉันชื่อว่าบ๊อบ

objectแต่ละตัวเก็บ properties แยกของใครของมัน … แต่ method ใช้ร่วมกัน

ขอยกตัวอย่างคลาสใหม่ที่ดูง่ายขึ้น ตามนี้

class Demo {
	int x
	function display() {
		print(this.x)
	}
}

คลาสง่ายๆ ไม่มีอะไรเลย มีแค่ property x และ method display เอาไว้แสดงค่า x เมื่อกี้เท่านั้น

จากนั้นเราก็สร้าง object จากคลาส Demo นี้ขึ้นมา 3 ตัวดังนี้

yi = new Demo()
yi.x = 1

er = new Demo()
er.x = 2

san = new Demo()
san.x = 3

yi.display()
er.display()
san.display()

object แต่ละตัวหลังจากสร้างขึ้นมาเสร็จแล้ว สิ่งที่แต่ละตัวจะมีคือ properties ทั้งหมดของคลาสต้นแบบ ในที่นี้คือตัวแปร x ตัวเดียว

อ้าว แล้ว method ล่ะ? ไม่ก๊อปปี้ตามมาด้วยเหรอ

คำตอบคือ ไม่!

(แต่ถ้าเราเรียนรู้ไปเรื่อยๆ จะพบว่ามีบางเคสที่ method ถูกก๊อปปี้ตามมาด้วยล่ะ แต่เราจะยังไม่พูดถึงมันในตอนนี้)

โดยปกติแล้ว object จะถือแค่ properties ของใครของมัน ใช้แยกกัน ไม่มีการใช้ร่วมกับผู้อื่น แต่เนื่องจาก method นั้นเป็นโค้ดที่ไม่สามารถเปลี่ยนแปลงค่าได้ ก็ไม่ใช่ตัวแปรนี่เนอะ object แต่ละตัวจึงมักจะมีการ refer (อ้างอิง) method ที่มันทำงานได้กลับไปหาโค้ดต้นฉบับที่เก็บอยู่ที่คลาสแทน

คีย์เวิร์ด this (หรือ self)

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

นั่นจึงเป็นที่มาของการใช้คีย์เวิร์ด this (เช่น Java, C++, C#, PHP, JavaScript) หรือ self (เช่น Swift, Python)

ไม่ว่าจะใช้คีย์เวิร์ดว่าอะไร มันก็ทำงานด้วยคอนเซ็ปเดียวกันนะ แต่จะหลังจากนี้จะขอใช้ this เป็นตัวแทนหลักนะ

เมื่อเราเรียกใช้งาน method ผ่าน object ตัวไหนก็ตามจะมีสิ่งที่เรียกว่า “บริบท” หรือ “context” เกิดขึ้น โดยมาตราฐานแล้วบริบทก็คือโค้ดนี้น่ะ กำลังรันอยู่ในมุมมองของใครอยู่ ถ้า object เป็นคนเรียกให้ method ทำงาน … บริบทของเราในครั้งนี้ก็คือ object ตัวนั้นนั่นเอง แล้วข้อกำหนดต่อมาก็คือถ้าต้องการอ้างอิงบริบทในตอนนั้นให้เรียกใช้งานผ่าน this

จากโค้ดตัวอย่างข้างบนจะเห็นว่า object ทั้ง 3 ตัวแม้จะถือค่า x แยกกันคนละตัว แต่ถ้าเกิดอยากเรียก display() ให้ทำงานก็จะเรียกกลับไปที่คลาสต้นแบบ แต่คลาสต้นแบบสามารถรู้ได้ว่าตอนนี้มันจะ print ค่า x โดยไปหยิบมาจาก object ตัวไหนก็โดยการดูว่า this หรือบริบทในที่นี้เป็นใครนั่นเอง

ข้อความระวังอย่างนึง คือในภาษายอดฮิตเกือบทุกภาษาในโลกนี้จะใช้ this แบบตรงไหตรงมาประมาณนี้ แต่ดันมีภาษานึง ที่การใช้ this มีท่ายากอยู่นั่นคือ JavaScript เอาเป็นว่าคอนเซ็ปการใช้ refer context ในภาษานั้นออกจะแปลกอยู่สักหน่อย (อ่านเพิ่มเติมได้ที่ เรื่องของ this ใน JavaScript)


 

เอาล่ะ  มาถึงตอนนี้เราก็ทบทวนเรื่อง class กับ object กันมาระดับนึงแล้ว เดี๋ยวบทความต่อไปจะมาพูดถึงการออกแบบโครงสร้างโค้ดในโลก OOP กัน

แต่ก่อนจะจบเรามาพูดถึงอีก 2 เรื่องที่โปรแกรมเมอร์มือใหม่มักจะลืมพวกมันไป แต่ดันเป็นเรื่องที่สำคัญมากๆๆ กันก่อนนะ

pointer กับโลก OOP

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

pointer คืออะไร?

ถ้าอธิบายง่ายๆ ต้องย้อนกลับไปดูตัวแปรประเภท primitive data type กันก่อน อย่างเช่นพวก int double char boolean เวลาเราประกาศตัวแปร แล้วกำหนดค่า (assign value) ให้พวกมัน “ค่า” ต่างๆ พวกนี้จะถูกเก็บอยู่ในตัวแปรเอง ถ้างงก็ดูรูปประกอบ

 แต่ สำหรับ object แล้วการประกาศตัวแปรจะแยกเป็น 2 ส่วน นั่นคือส่วนทางซ้าย (Left-Hand Side) ที่เป็นตัวแปรส่วน header และส่วน body ของตัว object จริงๆ ที่อยู่ทางขวา (Right-Hand Side) ตัวอย่างเช่น (ขอเรียกคุณ bob มาเป็นแขกรับเชิญอีกรอบนะ)

การที่เราสร้างคุณบ๊อบขึ้นมาจากคลาส Human จะต้องมีการสั่ง new วัตถุขึ้นมาชิ้นนึง

เพราะรายละเอียดของคลาสมีเยอะมาก วัตถุที่สร้างขึ้นมาจึงมีขนาดค่อนข้างใหญ่เมื่อเท่ากับข้อมูลประเภท primitive data type (มัน “อ้วน” นั่นเอง ฮา)

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

สรุปคือถึงแม้คุณจะไม่ชอบ pointer แค่ไหนแต่จงรู้ไว้ว่าทันทีที่คุณสร้าง object ขึ้นมามันก็มีเรื่องของ pointer โผล่เข้ามาแจมซะแล้ว และเมื่อมี pointer เรื่องต่อมาที่ต้องระวังก็คือ

//primitive data type
x = 5
y = x

//object type
bob = new Human()
wift = bob

การที่เราใช้ = (เท่ากับ, assign value) เพื่อเซ็คค่าให้ตัวแปรพวก primitive น่ะมันคือการ copy ค่าจากตัวหนึ่งไปไว้อีกตัวหนึ่ง หมายความว่าถ้าสั่งตามโค้ดข้างบนก็จะมีค่า 5 ซ้ำกัน 2 ตัว

แต่ถ้าเราทำแบบนั้นกับตัวแปร object บ้างอะไรจะเกิดขึ้น

ดูจากรูปข้างบน คำสั่ง wife = bob นั้นไม่ได้เป็นการ copy วัตถุของ Human เพิ่มขึ้นมาอีก 1 คน … ตอนนี้ก็ยังมี Human อยู่แค่ 1 คนเท่านั้นแหละ แต่ตัวแปรทั้ง 2 ตัวนั้น reference มายังวัตถุตัวเดียวกันซะงั้น

นั่นหมายความว่าถ้าเกิด Human มี properties ตัวนึงเป็น money ยอดเงินรวมในกระเป๋า … บ๊อบมีเงินในกระเป๋า … แต่ตอนนี้โดนภรรยาชี้มาที่นี่ด้วย แปลว่าตอนนี้ภรรยาของบ๊อบก็สามารถใช้เงินในกระเป๋าของบ๊อบได้เช่นกันไงล่ะ โดยใช้แล้วก็กระบทถึงเงินจริงๆ ของบ๊อบด้วยเพราะมันคือวัตถุชิ้นเดียวกันยังไงล่ะ (ฮา)

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

คีย์เวิร์ด static

กฎทุกกฎย่อมมีข้อยกเว้น แน่นอนว่ารวมถึง class-object ด้วย

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

ตัวอย่างเช่น ถ้าต้องการให้คลาส Human เก็บค่าจำนวนประชากร หรือ population ว่าตอนนี้มีคนทั้งหมดกี่คนแล้ว

มาวิเคราะห์กันสักหน่อย

– จำนวนประชากรเป็นสิ่งที่วัตถุ “มี” ไม่ใช่สิ่งที่วัตถุ “ทำได้” ดังนั้นมันเป็น properties
– แต่ถ้าเป็น properties แปลว่าวัตถุทุกตัวจะต้องเก็บค่านี้ทุกคน
– ค่า population นี้ต้องเท่ากันหมดด้วยนะ
– แล้วถ้ามีวัตถุบางตัวเกิดจัดการค่านี้ผิดล่ะ ทำให้ค่าเกิดไม่เท่ากัน ก็พังสิ!

ทางแก้คือในเมื่อเราต้องการใช้ population ค่าเดียว แทนที่จะเกิดไว้กับ object ก็เอาไปฝากไว้กับคลาสแทนสิ แล้วให้ทุกคนมาเรียกใช้ตัวแปรที่นี่แทน อ้าว ก็ไหนบอกว่า properties อยู่กับวัตถุนี่ ตามนั้นนะ

วิธีการสร้าง properties ที่จะเอาไปฝากไว้กับคลาสก็สร้างเหมือน properties ธรรมด๊า~ธรรมดา นี่แหละ แค่เติมคีย์เวิร์ด static ลงไปข้างหน้ามันดังนี้

class Human {
	static int population = 0
	...

ส่วนวิธีการใช้ก็เหมือนเดิมคือ

steve = new Human()
steve.population++

bill = new Human()
bill.population++

larry = new Human()
larry.population++

//ตอนนี้ population ของทุคนจะมีค่าเท่ากับ 3

แต่เพิ่มเพิมคือ … เนื่องมันการสั่งว่าตัวแปรนี้เป็น static เป็นตัวแปรของคลาส เราเลยสามารถเรียกใช้ตัวแปรนี้ “ผ่านคลาส” ตรงๆ ได้เลย อ้าว ไหนบอกว่าคลาสเป็นแค่ต้นแบบใช้ไม่ได้ไง หึหึ … กฎมีไว้แหกนะจ๊ะ

print( Human.population )
//output: 3

 

เอาล่ะ จบบทที่หนึ่ง กับการทบทวนเรื่อง class-object กันไปแล้ว ในบทต่อๆ ไปเราจะมาเข้าเรื่อง OOP ที่เข้มข้นกว่านี้กันนะ

3989 Total Views 2 Views Today
Ta

Ta

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

You may also like...

2 Responses

  1. Chart พูดว่า:

    อ่านแล้วเข้าใจ oop เพิ่มมากขึ้นเลยคับ
    เป็นบทความที่อ่านแล้วทำความเข้าใจได้ง่าย
    ขอบคุณนะครับ
    จะติดตามอ่านนะคับ(เข้าใจง่ายดี)

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *