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

บทความชุด: Object Oriented Programming (All About OOP)

รวมบทความเจาะลึกเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ ตั้งแต่แนวคิด วิธีการใช้งาน ตัวอย่างและหลักการใช้ต่างๆ เช่น 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 ที่เข้มข้นกว่านี้กันนะ

7603 Total Views 6 Views Today
Ta

Ta

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

You may also like...

2 Responses

  1. Chart พูดว่า:

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

ใส่ความเห็น

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