Skip to main content

OOP

Class ใน C++ คืออะไร

ในภาษา C++ class คือชนิดข้อมูลที่ผู้ใช้สามารถกำหนดขึ้นเองได้ เปรียบเสมือน “พิมพ์เขียว” สำหรับการสร้าง object โดย class ทำหน้าที่รวมเอาข้อมูล และ function ที่ทำงานกับข้อมูลนั้นๆเข้าไว้ด้วยกัน

คุณสมบัติสำคัญของ Class

  • Data Encapsulation class จะรวมข้อมูลต่างๆ (ตัวแปร) และ method เข้าไว้ด้วยกันเป็นหน่วยเดียว โดยการซ่อนข้อมูลเอาไว้ อันเป็นหลักการที่ซ่อนรายละเอียดภายในของ class จากภายนอก โดยจะเปิดเผยเฉพาะส่วนที่จำเป็นต้องใช้เท่านั้น
  • มี Constructors เป็น method พิเศษที่จะถูกเรียกใช้งานโดยอัตโนมัติเมื่อมีการสร้าง object ขึ้นมา มีหน้าที่กำหนดค่าเริ่มต้นให้กับคุณสมบัติ (attributes) ของ Object ส่วน Destructors จะถูกเรียกเมื่อ Object สิ้นสุดช่วงเวลาของมัน โดยมีหน้าที่ปลดปล่อยทรัพยากรที่ Object นั้นใช้
  • มีตัวระบุการเข้าถึง โดย class จะใช้ตัวระบุการเข้าถึงหรือ 'สิทธิ์การเข้าถึง' (public, protected, private) เพื่อควบคุมการเข้าถึงสมาชิกต่างๆ โดย public สามารถเข้าถึงได้จากทุกที่, protected เข้าถึงได้เฉพาะใน class ลูกที่สืบทอดมา, ส่วน private เข้าถึงได้เฉพาะภายใน class ที่ประกาศเท่านั้น
  • การสืบทอด (Inheritance) Class นั้นสามารถสืบทอดคุณสมบัติและพฤติกรรมของ class อื่นได้ เราเรียกอีก class นั้นว่า base class และ class ที่สืบทอดมาเป็น derived class (class ลูก) การสืบทอดเช่นนี้ช่วยทำให้สามารถนำ code ใน class เดิมกลับมาใช้ใหม่ได้ โดยไม่จำเป็นต้องเขียน code ใหม่อยู่ตลอดได้
  • ความหลากหลายเชิงรูปแบบ (Polymorphism) ภาษา C++ สนับสนุน polymorphism ผ่านการสืบทอดและ virtual functions ทำให้เราสามารถมอง Object ที่อยู่ใน class แตกต่างกันเสมือนอยู่ใน base class เดียวกันได้ อีกทั้งยังทำให้ส่วนติดต่อแบบเดียวกัน (interface) สามารถสื่อถึงข้อมูลหรือชนิดข้อมูลที่แตกต่างกันออกไปได้
  • มี Member Functions คือ function ที่ประกาศอยู่ภายใน class และสามารถเข้าถึงสมาชิกต่างๆของ class ได้ เราสามารถระบุได้ว่าจะให้ method นั้นเป็น const (ค่าคงที่ ที่ไม่สามารถแก้ไขตัว object), static (ใช้ร่วมกันโดยทุก Object ของ class), หรือ virtual (เพื่อสนับสนุนคุณสมบัติ polymorphism ใน OOP)

ตัวอย่างการใช้ class

#include <iostream>
#include <string>
using namespace std;

class Person {
public: // Access specifier
// Attributes
string name;
int age;

// Constructor
Person(string n, int a) : name(n), age(a) {}

// Method
void greet() const {
cout << "Hello, my name is " << name << " and I am " << age
<< " years old." << endl;
}
};

int main() {
// Creating an object of the Person class
Person person1("Alice", 30);

// Calling a method of the object
person1.greet();

return 0;
}

ตัวอย่างจาก code ด้านบนนี้ แสดงให้เห็นว่า

  • class Person ทำการห่อหุ้มคุณสมบัติต่างๆ (ชื่อและอายุ) เอาไว้
  • มี method สำหรับการทำงาน (greet) และ constructor เป็นตัวที่คอยกำหนดค่าเริ่มต้นให้กับคุณสมบัติของ Object ตอนที่ object Person นั้นถูกสร้างขึ้นมา
  • ในขณะที่ method greet จะใช้คุณสมบัติเหล่านั้นเพื่อแสดงข้อความสวัสดี (เป็นค่าที่ดึงมาจากที่ set ผ่าน constructor เข้ามา)
  • สิ่งที่เห็นนี้ช่วยให้รู้ว่า class ในภาษา C++ สามารถสร้างแบบจำลองของสิ่งที่มีอยู่ในโลกความเป็นจริงได้อย่างไร โดยการจัดกลุ่มข้อมูลและพฤติกรรมที่เกี่ยวข้องกันเข้าไว้ด้วยกัน

ตัวอย่างการใช้ class นี้เอง เราก็จะคุ้นๆตาจาก leet code มาเช่นกัน เนื่องจากใน leet code เองก็ใช้วิธีสร้าง class ในการรับข้อมูลไปใช้งานเช่นกัน

องค์ประกอบหลักของ Class

Instance

Instance ของ class คือการทำให้ class นั้นเกิดขึ้นจริงอย่างเป็นรูปธรรม ซึ่งจะถูกสร้างขึ้นในหน่วยความจำ โดยเราจะเรียกมันว่า object และมันจะห่อหุ้ม data members ที่ถูกนิยามไว้โดย class รวมถึงจะสามารถใช้ member functions ของมันได้

แต่ละ instance จะมีชุดค่า attribute เป็นของตัวเองซึ่งอาจแตกต่างจาก instances อื่น ทำให้เกิด data encapsulation และพฤติกรรมของ object ที่ไม่เหมือนกันออกมาได้

การสร้าง instance ของ class ประกอบด้วยการจัดสรรหน่วยความจำให้กับ object และการกำหนดค่าเริ่มต้นให้กับ attributes ของมัน โดยทั่วไปจะทำผ่าน constructor

นี่คือตัวอย่างของการประกาศ instance

class MyClass {
public:
MyClass() {} // Constructor
};

MyClass obj; // Creating an instance of MyClass named 'obj'

Constructor

Constructor คือ member function ชนิดพิเศษที่จะถูกเรียกใช้โดยอัตโนมัติเมื่อมีการสร้าง object ของ class นั้นขึ้นมา หน้าที่หลักของ constructor คือการกำหนดค่าเริ่มต้นให้กับ attribute ของ object โดย constructor จะมีชื่อเดียวกันกับ class และจะไม่คืนค่าใดๆกลับมาจากการเรียกใช้ constructor

จุดประสงค์ กำหนดค่าเริ่มต้นให้กับสถานะของ object (attributes) เมื่อมีการสร้างตัวอย่าง (instantiate) ขึ้นมา

คุณสมบัติ

  • สามารถถูก overload เพื่อรองรับการกำหนดค่าเริ่มต้นของ object ได้หลากหลายรูปแบบ
  • สามารถมี default parameter ได้
  • class หนึ่งสามารถมี default constructor (ไม่มีพารามิเตอร์) ได้ ซึ่งจะถูกใช้ในกรณีที่ไม่ได้มีการกำหนด constructor อื่นขึ้นมา หรือไม่ได้ระบุ constructor อย่างเจาะจงตอนสร้าง object

นี่คือตัวอย่างของ constructor

#include <iostream>
using namespace std;

// Define a class named 'Rectangle'
class Rectangle {
public:
int width, height;

// Constructor with two parameters
Rectangle(int w, int h) {
width = w;
height = h;
}

// Member function to calculate area
int area() { return width * height; }
};

int main() {
// Create an instance of Rectangle with width=5 and height=10
Rectangle rect(5, 10);

// Call the 'area' function on the instance
cout << "Area of Rectangle: " << rect.area() << endl;

return 0;
}

อธิบาย code ด้านบน

  • class Rectangle ถูกนิยามขึ้นมาพร้อมกับสมาชิกข้อมูล (data member) ที่เป็น public สองตัว (width และ height) และมี constructor สำหรับการสร้าง Object โดยที่ constructor จะรับพารามิเตอร์ 2 ตัว (w และ h) และทำการกำหนดค่าเริ่มต้นให้ความกว้าง (width) และความสูง (height) ของ Object ด้วยค่าที่ถูกส่งเข้ามา
  • constructor ในตัวอย่างนี้จะถูกนิยามในรูปแบบ Rectangle(int w, int h) ทำหน้าที่กำหนดค่าเริ่มต้นให้กับความกว้างและความสูงของ Object Rectangle ที่ถูกสร้างขึ้นมาใหม่ โดยใช้ค่าที่ได้รับเป็นพารามิเตอร์สำหรับการ set ค่าเริ่มต้นให้กับ w, h
  • instance ของ class Rectangle ถูกสร้างด้วยคำสั่ง Rectangle rect (5, 10); โดยบรรทัดนี้จะเป็นการสร้าง Object ชื่อ rect ขึ้นมา ซึ่งมีชนิดข้อมูลเป็น Rectangle โดยคำสั่งนี้จะเรียกใช้ constructor พร้อมกับส่งค่า 5 และ 10 ไปเป็น argument ส่งผลให้ width และ height ของ Object rect ถูกตั้งค่าเป็น 5 และ 10 ตามลำดับ
  • มีการเรียกใช้ member function ชื่อ area จะถูกเรียกใช้กับตัวอย่าง rect ด้วยคำสั่ง rect.area() โดย function นี้จะคำนวณและคืนค่าพื้นที่ของรูปสี่เหลี่ยมออกมา

Attribute

Attributes หรือที่รู้จักกันอีกชื่อว่า data members หรือ fields คือตัวแปรที่เก็บข้อมูลต่างๆ ที่เกี่ยวข้องกับ object ที่ถูกสร้างขึ้นมาจาก class โดย attribute บ่งบอกถึงสถานะ (state) ของ object นั้นๆ

จุดประสงค์

  • ทำหน้าที่จัดเก็บข้อมูลเกี่ยวกับ object หรือสถานะที่ object กำลังอยู่ในขณะนั้น
  • Attributes สามารถกำหนดการเข้าถึงได้เป็น public, private หรือ protected ซึ่งมีผลควบคุมการเข้าถึง attribute นั้นๆจากภายนอก class (เดี๋ยวเราอธิบายเรื่องการเข้าถึงอีกที)

อธิบายเพิ่มเติมจากตัวอย่างด้านบนของ Rectangle width และ height คือ Attribute ของ class Rectangle นั่นเอง

// Define a class named 'Rectangle'
class Rectangle {
public:
int width, height; // Attribute

// Constructor with two parameters
Rectangle(int w, int h) {
width = w;
height = h;
}

// Member function to calculate area
int area() { return width * height; }
};

Method

Methods หรือ member functions คือ function ที่ถูกนิยามไว้ภายใน class เพื่อให้สามารถทำงานกับ object ของ class นั้นๆได้ โดย method สามารถเข้าถึงและแก้ไขค่าของ attribute ต่างๆภายใน class รวมถึงเป็นตัวกำหนดพฤติกรรมการทำงานของ object

  • จุดประสงค์
    • กำหนดว่าจะมีการกระทำอะไรบ้างที่สามารถทำได้กับ object
    • จัดเตรียมวิธีการในการโต้ตอบกับข้อมูลภายใน object
  • ประเภทของ methods
    • Accessor Methods (Getters) ทำหน้าที่คืนค่าของ attribute ที่ถูกกำหนดการเข้าถึงเป็น private
    • Mutator Methods (Setters) ทำหน้าที่กำหนดค่า หรือแก้ไขค่าของ attribute ที่ถูกกำหนดการเข้าถึงเป็น private
    • General Methods ทำหน้าที่อื่นๆที่เกี่ยวข้องกับการทำงานของ class

และนี่คือตัวอย่างของการใช้ method ในแต่ละประเภท

#include <iostream>
using namespace std;

// Define a class named 'Student'
class Student {
private:
string name; // Private attribute
int age; // Private attribute

public:
// Constructor to initialize the attributes
Student(string n, int a) {
name = n;
age = a;
}

// Getter for the name
string getName() const { return name; }

// Setter for the name
void setName(string n) { name = n; }

// Getter for the age
int getAge() const { return age; }

// Setter for the age
void setAge(int a) { age = a; }

// General method to display student details
void displayDetails() {
cout << "Student Name: " << name << ", Age: " << age << endl;
}
};

int main() {
// Create an instance of Student
Student student1("John Doe", 20);

// Display initial details
student1.displayDetails();

// Modify the student's name and age using setters
student1.setName("Jane Doe");
student1.setAge(21);

// Display updated details
student1.displayDetails();

return 0;
}

อธิบายจาก code ด้านบน

  • Class Student ถูกนิยามขึ้นมาพร้อมกับ attribute แบบ private สองตัวคือ name และ age โดย attribute ที่เป็น private จะสามารถเข้าถึงได้เฉพาะภายใน class ทำให้มั่นใจได้ในเรื่องของการห่อหุ้มข้อมูล (encapsulation) ว่าจะไม่โดนรบกวนจากภายนอกได้
  • constructor ที่มีชื่อว่า Student (string n, int a) ทำหน้าที่กำหนดค่าเริ่มต้นให้กับ private attribute ที่ชื่อ name และ age ด้วยค่าที่ถูกส่งเข้ามา
  • class นี้จะมีทั้ง method แบบ getter และ setter สำหรับทั้ง name และ age โดยที่ getter (getName และ getAge) ทำหน้าที่คืนค่าของ private attribute ในขณะที่ setter (setName และ setAge) ให้เราสามารถแก้ไขค่าของ private attribute ได้ วิธีการแบบนี้เป็นการห่อหุ้ม attribute และควบคุมการเข้าถึงค่า private เอาไว้ ไม่ให้สามารถแก้ไขจากภายนอกโดยตรงได้
  • method displayDetails เป็น general method ที่ใช้ private attributes ในการแสดงรายละเอียดของนักเรียน (class Student)
  • และใน main function ทำการสร้าง instance ของ Student, แสดงรายละเอียดเริ่มต้น, ปรับเปลี่ยนค่าของ attribute ของ instance ด้วยการใช้ setter และจากนั้นแสดงรายละเอียดที่ถูกอัพเดทออกมาได้

ทีนี้เพื่อเติมเต็มความเข้าใจที่เพิ่มขึ้นจะขออธิบายเรื่องของ private และ public เพิ่มเติม

Public / Private

  • Public สมาชิก (member) ที่ถูกประกาศเป็น public สามารถเข้าถึงได้จากทุกที่ที่อยู่นอก class ที่ Object นั้นถูกสร้างขึ้นมา หมายความว่า function หรือ class ภายนอกใดๆก็สามารถเข้าถึงและปรับเปลี่ยน public member ได้
  • Private สมาชิกที่ถูกประกาศเป็น private จะเข้าถึงได้เฉพาะภายใน class นั้นๆเท่านั้น จะไม่สามารถเข้าถึงหรือเปลี่ยนแปลงค่าได้โดยตรงจากภายนอก class การเข้าถึง private member มักจะทำผ่าน public method ต่างๆ (เช่น ตัวอย่าง getters และ setters ในหัวข้อ method)

ตัวอย่าง code

#include <iostream>
using namespace std;

// Define a class named 'Person'
class Person {
private:
string name; // Private attribute: Not accessible outside the class directly
int age; // Private attribute: Not accessible outside the class directly

public:
// Public Constructor: Accessible from outside the class
Person(string n, int a) {
name = n;
age = a;
}

// Public method to set the name - demonstrates encapsulation
void setName(string n) {
name = n; // Can access private member within the class
}

// Public method to get the name
string getName() const {
return name; // Can access private member within the class
}

// Public method to display details of the person
void displayDetails() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};

int main() {
// Create an instance of Person
Person person1("Alice", 30);

// Attempt to access public methods
cout << "Initial Details: " << endl;
person1.displayDetails();

// Set a new name using public setter method
person1.setName("Bob");

// Display updated details using public method
cout << "Updated Details: " << endl;
person1.displayDetails();

// Attempt to directly access private members (This will cause a compile-time
// error if uncommented) cout << person1.name; // ERROR: 'name' is private
// within this context

return 0;
}

ตัวอย่างการใช้งานแบบรวมกัน

นี่คือตัวอย่าง code C++ ที่แสดงให้เห็นถึงการใช้ constructors, attributes (data members), methods, สิทธิ์การเข้าถึงแบบ public และ private รวมถึงการสร้าง instances ภายใน class โดยตัวอย่างนี้จะใช้กับ class แบบง่ายๆชื่อว่า Car

#include <iostream>
#include <string>
using namespace std;

class Car {
public:
// Constructor
Car(string model, int year) : model(model), year(year) {}

// Public method to display car details
void displayInfo() const {
cout << "Car model: " << model << ", Year: " << year << endl;
}

// Public method to change the car's model
void setModel(string newModel) { model = newModel; }

// Public method to get the car's model
string getModel() const { return model; }

private:
// Private attributes
string model;
int year;
};

int main() {
// Creating an instance of Car
Car myCar("Toyota", 2020);

// Using a public method to display car information
myCar.displayInfo();

// Changing the model of the car
myCar.setModel("Honda");

// Displaying the updated car information
myCar.displayInfo();

return 0;
}

Explanation

  • class Car ถูกนิยามขึ้นมาพร้อมด้วย attribute แบบ private 2 ตัว (model และ year) โดยมี constructor และ method แบบ public อีก 3 ตัว (displayInfo, setModel และ getModel)
  • มี Constructor ที่มีชื่อ Car(std::string model, int year) ทำหน้าที่กำหนดค่าเริ่มต้นให้กับ object Car ด้วยค่า model และ year โดย constructor เป็น function พิเศษที่จะถูกเรียกใช้งานทุกครั้งที่มีการสร้าง instance ใหม่ของ Car
  • มี Attributes ชื่อ model และ year ทำหน้าที่เก็บข้อมูลของแต่ละ instance ของ Car โดยจะถูกกำหนดสิทธิ์การเข้าถึงเป็น private หมายความว่าจะไม่สามารถเข้าถึงได้โดยตรงจากภายนอก class ได้
  • Methods ที่มี
    • displayInfo() เป็น method แบบ public ที่จะพิมพ์รายละเอียดของรถออกมาที่ console และสามารถเข้าถึง attribute ที่เป็น private ได้ เนื่องจากเป็นสมาชิกของ class Car ด้วยเช่นกัน
    • setModel(std::string newModel) ทำให้เราสามารถเปลี่ยนแปลง model ของรถได้ โดย method นี้จะทำการแก้ไขค่าของ attribute ชื่อ model ภายใน instance ตัวนั้นได้
    • getModel() ทำหน้าที่คืนค่า model ปัจจุบันของรถออกมา ถือเป็นตัวอย่างของ accessor method (หรือที่เรียกว่า getter) ซึ่งถือเป็นวิธีการที่ปลอดภัยในการอ่านค่าของ attribute ที่เป็น private
  • Public and Private Access Specifiers
    • public members (displayInfo, setModel, getModel) สามารถเข้าถึงได้จากภายนอก class ทำให้เราสามารถโต้ตอบกับ object ของ Car ได้
    • private members (model, year) จะถูกห่อหุ้มไว้ภายใน class ทำหน้าที่ปกป้องสถานะภายในของ object Car จากการถูกแก้ไขจากภายนอก class
  • การสร้าง instance ใน function main() มีการสร้าง instance ของ Car ชื่อว่า myCar โดยส่งค่าเริ่มต้นเข้าไปว่า "Toyota" และ 2020 ใน instance นี้จะสังเกตว่ามีการสร้างตัวอย่าง (instantiate) ขึ้นมาจาก class
  • การใช้ Public Methods ในตัวอย่างมีการใช้ method displayInfo และ setModel เพื่อโต้ตอบกับ object myCar ซึ่งตัวอย่างตรงนี้ช่วยให้เราเห็นว่า methods สามารถนำมาใช้ทำงานหรือเข้าถึงข้อมูลใน object ได้

และนี่คือตัวอย่างเบื้องต้นของ code แนว OOP โดยอ้างอิงผ่าน Class

OOP คืออะไร

Object-Oriented Programming (OOP) ในภาษา C++ เป็น programming paradigm ที่มีแนวคิด "objects" เป็นหัวใจสำคัญ ซึ่ง object นั้นสามารถเก็บข้อมูลในรูปของ fields (หรืออาจเรียกว่า attributes หรือ properties) และ code ในรูปของ procedures (หรืออาจเรียกว่า methods หรือ functions) OOP มีจุดมุ่งหมายเพื่อทำการจำลองสิ่งที่มีอยู่ในโลกความเป็นจริงลงมาในการเขียนโปรแกรม อย่างเช่น การสืบทอด (inheritance) การซ่อนข้อมูล (hiding) และ ความหลากหลายเชิงรูปแบบ (polymorphism) หลักการพื้นฐานสำคัญของ OOP ในภาษา C++ ได้แก่

1. Encapsulation Encapsulation คือกลไกที่ใช้จำกัดการเข้าถึงส่วนประกอบบางอย่างของ object และปกป้องการแก้ไขส่วนประกอบเหล่านั้นที่ไม่ได้รับอนุญาตไม่ให้สามารถเข้าถึงได้ โดยเราจะใช้ access specifiers (private, protected และ public) มาควบคุมการมองเห็นของสมาชิกของ class โดย Encapsulation จะช่วยในการนำเอาข้อมูลและ method ที่จะทำงานกับข้อมูลนั้นมารวมไว้เป็นหน่วยเดียวกันได้

2. Inheritance Inheritance คือการที่ class หนึ่งๆ (class ลูก) สามารถรับเอาคุณสมบัติหรือพฤติกรรมของ class อื่น (class แม่) ไปใช้งานได้ ซึ่งทำให้เราสามารถ Override (เขียนทับ) หรือต่อยอด (Extend) function ต่างๆได้ สิ่งนี้ช่วยเสริมการนำ code กลับมาใช้ใหม่ได้ และสร้างความสัมพันธ์แบบลำดับชั้นระหว่าง class ต่างๆออกมาได้

3. Abstraction Abstraction คือแนวคิดของการซ่อนรายละเอียดการทำงานที่ซับซ้อนเอาไว้ และแสดงเฉพาะ feature หลักๆของ object นั้นไว้เท่านั้น (ประกาศว่า class นั้นทำอะไรออกมาได้แค่นั้น) โดยในภาษา C++ abstraction สามารถทำได้โดยใช้ abstract class และ interface สิ่งนี้ทำให้เราสามารถมุ่งความสนใจไปที่ว่า object นั้นทำอะไร ไม่ใช่ว่ามันทำงานอย่างไร

4. Polymorphism Polymorphism ทำให้ส่วนติดต่อ interface หนึ่งๆสามารถสื่อถึง forms (หรือชนิดข้อมูล) ที่แตกต่างกันออกไปได้ ในภาษา C++ polymorphism สามารถทำได้ผ่านการใช้ function overloading, operator overloading และ virtual function โดย virtual function จะทำให้ override method ได้ สิ่งนี้ช่วยให้เราสามารถปฏิบัติกับ object ที่อยู่ในคนละ class เสมือนเป็น object ที่อยู่ใน superclass เดียวกันได้

นี่คือตัวอย่าง code ++ ที่ใช้คุณสมับิติ OOP

#include <iostream>
#include <string>
using namespace std;

// Base class
class Animal {
public:
virtual void
speak() const = 0; // Pure virtual function making Animal an abstract class
};

// Derived class

class Dog : public Animal {
public:
void speak() const override { cout << "Woof!" << endl; }
};

int main() {
Dog myDog;
myDog.speak(); // Polymorphism: Dog class overrides the speak method

return 0;
}

จากตัวอย่าง code นี้

  • class Animal เป็น abstract base class ที่มี pure virtual function ชื่อ speak() ซึ่งแสดงถึงหลักการ abstraction
  • ส่วน class Dog จะสืบทอดคุณสมบัติมาจาก Animal และทำการ override (เขียนทับ) method speak() ซึ่งตรงนี้จะเห็นหลักการ inheritance และ polymorphism
  • การใช้ access specifier (public) เป็นตัวอย่างของ encapsulation ซึ่งทำให้มั่นใจว่า method speak() สามารถเข้าถึงได้จากภายนอก class ในขณะที่รายละเอียดการทำงานอื่นๆอาจจะถูกซ่อนไว้ภายในของ class

OOP ในภาษา C++ ทำให้การสร้างโปรแกรมที่ซับซ้อนแต่สามารถจัดการได้ง่ายผ่านการมองของทุกอย่างออกเป็น object โดยสามารถสะท้อนพฤติกรรมและปฏิสัมพันธ์แบบที่อยู่ในโลกความเป็นจริงได้เช่นกัน

1. Encapsulation (การห่อหุ้ม)

Encapsulation เป็นแนวคิดพื้นฐานที่สำคัญของ OOP ซึ่งเป็นการรวมกลุ่มข้อมูล (attributes) และ method (functions) ที่ทำงานกับข้อมูลนั้นๆเข้าไว้ด้วยกันเป็นหน่วยเดียว เรียกว่า class นอกจากนี้ยังเป็นการจำกัดการเข้าถึงการทำงานภายในของ class ด้วย ซึ่งหลักการนี้เรียกว่าการซ่อนข้อมูล (data hiding)

Encapsulation เป็นการทำให้แน่ใจได้ว่าสถานะภายในของ object นั้นจะไม่สามารถเข้าถึงได้โดยตรงจากภายนอกได้ เราจำเป็นใช้งานกับข้อมูลของ object ผ่านทาง method เท่านั้น

ประโยชน์ของ Encapsulation

  • ควบคุมวิธีการเข้าถึงหรือแก้ไขข้อมูล
  • เพิ่มความปลอดภัยให้กับสถานะภายในของ object
  • สามารถเปลี่ยนแปลงการทำงานภายในโดยไม่กระทบกับส่วนอื่นๆของโปรแกรม

ตัวอย่างของ Encapsulation ในภาษา C++

เรามาดูตัวอย่างง่ายๆที่แสดงถึง encapsulation ในภาษา C++ กัน โดยเราจะใช้ class ชื่อ Person ซึ่งจะห่อหุ้ม (encapsulate) ชื่อและอายุของคนหนึ่งเอาไว้ด้วยการควบคุมการเข้าถึงคุณสมบัติเหล่านี้ผ่านทาง public methods เอาไว้

#include <iostream>
#include <string>
using namespace std;

class Person {
private:
string name; // Private attribute
int age; // Private attribute

public:
// Constructor
Person(string n, int a) : name(n), age(a) {}

// Setter for age - Demonstrates validation
void setAge(int newAge) {
if (newAge >= 0) { // Ensure the new age is sensible
age = newAge;
}
}

// Getter for age
int getAge() const { return age; }

// Getter for name
string getName() const { return name; }
// A public method that uses private data
void greet() const {
cout << "Hello, my name is " << name << " and I am " << age
<< " years old." << endl;
}
};

int main() {
Person person("Alice", 30); // Create a Person object
person.greet(); // Access a public method

// Attempt to set an invalid age
person.setAge(-5);

// The age won't change due to validation in setAge
cout << person.getName() << "'s age is " << person.getAge() << endl;

return 0;
}

จาก code ด้านบน

  • attribute ที่ชื่อ name และ age นั้นถูก set เป็น private ซึ่งทำให้ไม่สามารถเข้าถึงได้โดยตรงจากภายนอก class ได้
  • method setAgegetAgegetName และ greet ถูกกำหนดให้เป็น public ทำให้เราสามารถเข้าถึงข้อมูลส่วนตัว (private data) ได้ โดย method setAge จะมีการตรวจสอบความถูกต้อง (validation) เพื่อป้องกันการกำหนดค่าอายุที่ไม่ถูกต้องด้วย (เช็คว่ามากกว่า 0 หรือไม่)
  • ผู้ที่ใช้ class Person จำเป็นต้องเข้าถึงข้อมูลของ object ผ่านทาง public methods เหล่านี้เท่านั้น ไม่สามารถเข้ามาแก้ไขข้อมูลโดยตรงได้ และทำให้มั่นใจได้ว่า การ set ข้อมูลที่เกิดขึ้น เกิดขึ้นอย่างถูกต้องแล้วผ่าน method ที่กำหนดเอาไว้แล้วเช่นกัน

2. Inheritance (การสืบทอด)

Inheritance เป็นกลไกที่เปิดช่องทางให้ class หนึ่งๆ (เรียกว่า derived class) สามารถสืบทอด attributes และ methods ต่างๆจากอีก class หนึ่ง (เรียกว่า base class) มาได้ ถือได้ว่าเป็นเสาหลักสำคัญของ OOP ที่อำนวยความสะดวกให้เกิดการนำ code กลับมาใช้ใหม่ได้ และสร้างลำดับชั้นโครงสร้างของ class ขึ้นมา

ประโยชน์ของ Inheritance

  • Inheritance ส่งเสริมการนำ code ที่มีอยู่แล้วกลับมาใช้ได้ โดยที่ derived class สามารถสืบทอด properties และพฤติกรรมต่างๆที่เป็น public และ protected ทั้งหมดมาจาก base class ทำให้คุณสามารถเพิ่ม feature หรือจะ override (เขียนทับ) ของที่มีอยู่เดิมลงไปได้
  • Inheritance สามารถสะท้อนธรรมชาติตามลำดับขั้นของสิ่งที่มีอยู่ในโลกความเป็นจริงได้ (ใครสืบทอดต่อจากใคร ใช้ของใคร มาจากใคร)
  • support คุณสมบัติตัวอื่นๆ เช่น abstract, polymorphism

ตัวอย่างการใช้ Inheritance ใน C++

นี่คือตัวอย่างแสดงการสืบทอด โดยเป็นการสืบทอด class Car มาจาก base class ****Vehicle

#include <iostream>
#include <string>
using namespace std;

// Base class
class Vehicle {
protected: // Accessible by derived class
string brand;

public:
Vehicle(string b) : brand(b) {} // Constructor

void honk() const { cout << "Beep beep! This is a " << brand << ".\n"; }
};

// Derived class
class Car : public Vehicle { // Inherits from Vehicle
private:
string model;

public:
Car(string b, string m) : Vehicle(b), model(m) {} // Constructor

void showModel() const { cout << "It is a " << model << ".\n"; }
};

int main() {
Car myCar("Toyota", "Corolla");
myCar.honk(); // Accessing method from the base class
myCar.showModel(); // Accessing method from the derived class
return 0;
}

จาก code ด้านบน

  • Base Class (Vehicle) จะเป็นตัวนิยามแนวคิดของยานพาหนะแบบทั่วๆไป โดยจะมี protected attribute คือ brand และ public method อย่าง honk()
  • Derived Class (Car) สืบทอดคุณสมบัติต่างๆมาจาก Vehicle แต่จะมี attributes กับ methods ที่เจาะจงกว่าอย่างเช่น model และ showModel() โดย constructor ของ Car จะทำหน้าที่กำหนดค่าเริ่มต้นให้กับ attributes ของทั้งของ Car เองและของ Vehicle ด้วย
  • การใช้ brand นั้นจะเกิดขึ้นใน base class โดยสามารถเรียกใช้ได้จากคำสั่งที่สืบทอดมา honk() และการเพิ่ม protected เข้าไปนั้น เพื่อทำให้มั่นใจว่าจะสามารถเข้าถึง brand ได้จากภายใน derived class แต่ไม่สามารถเข้าถึงได้จากภายนอกได้
  • object ที่ชื่อ myCar ซึ่งถูกสร้างขึ้นจาก class Car จะสามารถใช้ได้ทั้ง method honk() ที่สืบทอดมาจาก Vehicle และ method showModel() ที่เป็นของตัวเองได้

และนี่ก็คือตัวอย่างของ inheritance ตัวอย่างนี้ช่วยทำให้เห็นว่า inheritance ช่วยเอื้อให้เราสามารถต่อยอดและปรับแต่งลักษณะเฉพาะของ base class ได้โดยไม่ต้องแก้ไขตัว base class โดยตรง ส่งเสริมให้ code มีความเป็น modular และตัว code นั้นก็สามารถนำกลับมาใช้ได้ใหม่ใน class ที่สืบทอดต่อไปได้

เพิ่มเติมเรื่อง Protected

ในภาษา C++ access specifier ชื่อ protected จะถูกใช้ในการประกาศว่าสมาชิกใน class นั้นๆจะไม่สามารถเข้าถึงได้โดยตรงจากภายนอก class แต่ “จะสามารถเข้าถึงได้ภายใน class ที่สืบทอดมา” (derived classes) สิ่งนี้จะต่างจาก private member ซึ่งจะไม่สามารถเข้าถึงหรือแม้แต่จะมองเห็นได้จากภายนอก class ที่มันถูกประกาศไว้ได้เลย (ซึ่งรวมไปถึง derived class ด้วยสำหรับ private)

เรามาดูตัวอย่างประกอบที่แสดงให้เห็นว่า protected นั้นทำงานอย่างไรในภาษา C++ โดยจะมีการใช้ base class ที่มี protected member และ derived class ที่จะทำการเข้าถึง protected member นั้น

#include <iostream>
using namespace std;

// Base class
class Animal {
protected:
string name; // Protected member can be accessed by derived classes

public:
Animal(string n) : name(n) {} // Constructor to initialize name

void display() { cout << "Animal name: " << name << endl; }
};

// Derived class
class Dog : public Animal {
public:
Dog(string n) : Animal(n) {} // Constructor: Initialize base class member name

void setName(string newName) {
name =
newName; // Direct access to protected member 'name' from the base class
}

void bark() {
cout << name << " says Woof!" << endl; // Access to protected member 'name'
}
};

int main() {
Dog dog1("Buddy");
dog1.display(); // Access to public member function of the base class

// Modify the protected member 'name' through a method in the derived class
dog1.setName("Max");
dog1.bark(); // Demonstrates that the protected member 'name' was successfully
// modified

return 0;
}

คำอธิบายจาก code

  • Base Class (Animal) class นี้จะมี protected member ชื่อ name ซึ่งหมายความว่า member ตัวนี้สามารถเข้าถึงได้จากภายใน class Animal และจาก class อื่นๆที่สืบทอดมาจาก Animal นอกจากนี้ยังมี public method ชื่อ display ที่สามารถใช้พิมพ์ชื่อของ Animal ได้
  • Derived Class (Dog) สืบทอดคุณสมบัติมาจาก Animal สามารถเข้าถึง protected member ชื่อ name ได้โดยตรงจากภายใน method ของตัวเองได้เลย ซึ่งการกำหนดค่าผ่านทาง setName ก็ยังนับว่าทำจากภายในสมาชิกของ Dog ส่วน method bark() แสดงให้เห็นการเข้าถึง protected member ที่ชื่อว่า name ออกมาได้
  • ตัวอย่างนี้แสดงให้เห็นว่า class Dog ซึ่งสืบทอดมาจาก Animal สามารถเข้าถึงและแก้ไขค่าของ protected member ชื่อ name ได้ สิ่งนี้จะไม่สามารถทำได้เลยถ้าหาก name ถูกประกาศเป็น private member ใน class Animal ไว้

3. Abstraction (การแยกส่วน)

Abstraction เป็นหลักการที่เกี่ยวกับการซ่อนรายละเอียดการทำงานที่ซับซ้อนของระบบ และเปิดเผยเฉพาะส่วนที่จำเป็นให้ผู้ใช้เห็น ในภาษา C++ เราสามารถทำให้ abstraction เกิดขึ้นได้โดยใช้ abstract classes และ interfaces (pure virtual functions ที่ไม่มีการ implement เดี๋ยวเรามาอธิบายอีกทีว่าทำกันอย่างไร) วิธีการนี้ทำให้เราสามารถเน้นไปที่การกระทำที่ object นั้นทำได้ มากกว่าจะไปลงรายละเอียดถึงวิธีการทำงาน และเปิดโอกาสให้เราสามารถทำความเข้าใจ object เหล่านั้นได้ง่ายขึ้นจากภายนอกได้

ประโยชน์ของ Abstraction

  • ทำให้มุมมองของระบบที่ซับซ้อนนั้นง่ายขึ้นด้วยการแสดงเฉพาะข้อมูลที่เกี่ยวข้อง
  • ทำให้เราสามารถมุ่งความสำคัญไปกับการโต้ตอบ (behavior) ได้โดยไม่ต้องเข้าใจรายละเอียดอันซับซ้อนทั้งหมด
  • สามารถนำ code กลับมาใช้ใหม่ ด้วยการแยกส่วน interface (ต้นแบบของ class) ของระบบออกจากการ implement (class ที่มีการทำงานจริงๆ)

ตัวอย่างของ Abstraction ใน C++

ตัวอย่างนี้จะแสดงให้เห็นถึง abstraction โดยการกำหนด abstract base class ที่ชื่อ Shape ซึ่งจะหมายถึงรูปทรงเรขาคณิตแบบทั่วไปที่มี method ไว้คำนวณพื้นที่ กับรูปทรงเลขาคณิตแต่ละประเภท อย่างเช่น Circle และ Rectangle จะสืบทอดมาจาก Shape และทำการ implement method calculateArea โดยอิงตามสูตรการคำนวณพื้นที่ของรูปทรงนั้นๆ

#include <cmath> // For M_PI
#include <iostream>
using namespace std;

// Abstract base class
class Shape {
public:
// Pure virtual function
virtual double
calculateArea() const = 0; // No implementation, must be overridden

virtual ~Shape() {} // Virtual destructor for safe polymorphic use
};

// Derived class
class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {}

double calculateArea() const override {
return M_PI * radius * radius; // Implementation for circle
}
};

// Another derived class
class Rectangle : public Shape {
private:
double length, width;

public:
Rectangle(double l, double w) : length(l), width(w) {}

double calculateArea() const override {
return length * width; // Implementation for rectangle
}
};

int main() {
Circle circle(5);
Rectangle rectangle(10, 20);

cout << "Circle area: " << circle.calculateArea() << endl;
cout << "Rectangle area: " << rectangle.calculateArea() << endl;

return 0;
}

จาก code ด้านบน

  • Abstract Base Class (Shape) ทำหน้าที่เป็นแม่แบบ (template) ให้กับรูปทรงต่างๆ โดยทำการนิยาม pure virtual function ชื่อ calculateArea() ขึ้นมา ทำให้ Shape เป็น abstract class ที่ไม่สามารถจะสร้าง instance ขึ้นมาตรงๆได้
  • Derived Classes (Circle, Rectangle) ทำการ implement abstract method ที่ชื่อ calculateArea() ซึ่งได้มาจาก Shape class (base class) เพื่อกำหนดสูตรคำนวณพื้นที่เฉพาะเจาะจงลงไปสำหรับแต่ละประเภทของรูปทรง อันนี้แสดงให้เห็นถึง polymorphism ซึ่งเป็นลักษณะเด่นสำคัญของ abstraction ที่ interface เพียงตัวเดียว (calculateArea()) สามารถเป็นตัวแทนของฟอร์มซึ่งซ่อนอยู่เบื้องหลังอันหลากหลาย (เดี๋ยวเรามาขยี้เพิ่มกันอีกทีในหัวข้อ polymorphism

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

เพิ่มเติมเรื่อง Virtual

ในภาษา C++ keyword virtual จะถูกใช้กับการประกาศ class เพื่อระบุว่า function นี้สามารถถูก override (เขียนทับ) ได้ใน derived classes โดยเมื่อเราระบุว่า member function ตัวไหนเป็น virtual ใน base class ทาง C++ จะทำการตั้งค่ากลไกต่างๆที่จะทำให้มั่นใจว่าการเรียกใช้งาน function นั้นจะถูก resolved ในระหว่างที่โปรแกรมทำงานจริงได้ ซึ่งจะเป็นการดูจากการเรียกใช้งานจริงของ object ที่เรียกใช้งาน function นั้นได้

จุดหลักของการใช้ virtual function

  • Override ใน Derived Class derived class สามารถ override virtual function จาก base class เพื่อใส่การทำงาน (implementation) ที่เฉพาะเจาะจงลงไปได้ โดยยังคงมีส่วนติดต่อ interface แบบเดียวกับใน base class
  • Base Class Pointer หรือ Reference ถ้ามี pointer หรือ reference ของ base class เราสามารถที่จะเรียกใช้งาน virtual function ได้ ซึ่ง C++ จะเรียกใช้เวอร์ชั่นของ function ที่เหมาะสมกับชนิดข้อมูลที่แท้จริงของ object นั้นๆมาได้
  • Virtual Destructor ในกรณีที่มีการล้างค่า object ผ่านทาง pointer ของ base class การประกาศว่า destructor เป็น virtual จะทำให้มั่นใจว่า destructor ของ derived class จะถูกเรียกใช้งานด้วย ทำให้ล้างค่าทรัพยากรได้อย่างถูกต้อง

ตัวอย่าง code การใช้งาน virtual

#include <iostream>
using namespace std;

class Base {
public:
virtual void show() const { // Virtual function
cout << "Base show" << endl;
}
virtual ~Base() {} // Virtual destructor
};

class Derived : public Base {
public:
void show() const override { // Override the base class function
cout << "Derived show" << endl;
}
};

void callShow(const Base &obj) {
obj.show(); // Dynamic binding happens here
}

int main() {
Base base;
Derived derived;

callShow(base); // Calls Base::show
callShow(derived); // Calls Derived::show, thanks to dynamic binding

return 0;
}

จาก code ด้านบน

  • Virtual Function show ถูกประกาศใน Base class และมีการ override (เขียนทับ) ใน Derived class การใช้คำว่า virtual จะทำให้มั่นใจว่าเมื่อมีการเรียกใช้ show สามารถเรียกใช้เวอร์ชั่นที่ถูกต้องออกมาได้ โดยจะขึ้นอยู่กับชนิดข้อมูลของ object นั้น
  • Dynamic Binding ใน function callShow ถึงแม้ว่าชนิดข้อมูลของ parameter จะถูกระบุว่าเป็น reference ของ Base แต่การเรียกใช้งาน obj.show() จะใช้ dynamic binding โดยจะเลือกเวอร์ชั่น function show ที่ถูกต้องในขณะที่โปรแกรมทำงานอยู่
  • Virtual Destructor เพื่อทำให้แน่ใจว่า destructor ของ derived class จะถูกเรียกใช้งานอย่างถูกต้องในขณะที่มีการลบข้อมูลของ object (สัญลักษณ์ ~ จะถูกใช้ในตอนประกาศ destructor และถูกวางไว้เป็นคำนำหน้าชื่อของ class โดยตัว destructor นี้คือ member function ชนิดพิเศษที่จะถูกเรียกใช้งานโดยอัตโนมัติในตอนที่มีการลบข้อมูล object ออกไป สิ่งที่ destructor ทำเป็นหลักๆคือการปลดปล่อยทรัพยากรต่างๆ อย่างเช่นการปิดไฟล์หรือการลบ allocated memory ที่อาจถูกดึงไปใช้ร่วมกับ object ตอนที่ object นั้นยังมีชีวิตอยู่ ก่อนที่ข้อมูลของ object นั้นๆจะถูกทำลายไป)

4. Polymorphism (การมีหลายรูปแบบ)

Polymorphism เป็นแนวคิดที่เปิดช่องทางให้ object ที่อยู่ในคนละ class กันสามารถปฏิบัติตัวเสมือนเป็น object ที่อยู่ใน superclass เดียวกันได้ polymorphism ถือเป็นแกนกลางสำคัญของ OOP ที่ทำให้ส่วนติดต่อ “interface เพียงตัวเดียว” สามารถเป็นตัวแทนของ form ที่ซ่อนอยู่ภายในที่แตกต่างกันได้ (ชนิดข้อมูลต่างชนิดกัน)

ในภาษา C++ polymorphism ส่วนมากจะสามารถทำให้สำเร็จได้ผ่านการใช้ virtual function ซึ่งอนุญาตให้ derived class ทำการ override method ของ base class

ตัวอย่าง Polymorphism ใน C++

ด้านล่างนี้คือตัวอย่างแสดงให้เห็นถึง runtime polymorphism ซึ่งเกิดขึ้นจากการใช้ virtual function และ pointer ไปยัง object ใน base class

#include <iostream>
using namespace std;

// Base class
class Shape {
public:
// Virtual function
virtual void draw() const { cout << "Drawing a shape" << endl; }
virtual ~Shape() {} // Virtual destructor for safe polymorphic deletion
};

// Derived class 1
class Circle : public Shape {
public:
void draw() const override { // Override base class method
cout << "Drawing a circle" << endl;
}
};

// Derived class 2
class Rectangle : public Shape {
public:
void draw() const override { // Override base class method
cout << "Drawing a rectangle" << endl;
}
};

void drawShape(const Shape *shape) {
shape->draw(); // Polymorphic call
}

int main() {
Circle circle;
Rectangle rectangle;

drawShape(&circle); // Calls Circle's draw
drawShape(&rectangle); // Calls Rectangle's draw

return 0;
}

จาก code ด้านบน

  • Base Class (Shape) มี virtual function ชื่อ draw() ซึ่งทำหน้าที่เป็น interface พื้นฐานสำหรับการวาดรูปทรงต่างๆ
  • Derived Classes (Circle, Rectangle) แต่ละ class จะทำการ override (เขียนทับ) method draw() เพื่อระบุลงไปว่าในกรณีของ Class ตัวเอง (Circle และ Rectangle) จะทำการวาดอย่างไร
  • Polymorphic Function (drawShape) จะรับ pointer ของ base class ชื่อ Shape อันนี้ทำให้ function drawShape สามารถชี้ไปยัง object ใน class ไหนก็ได้ที่สืบทอดมาจาก Shape ทำให้สามารถเรียกใช้งาน method draw() ที่ถูก override แล้วในขณะที่โปรแกรมทำงานอยู่ได้ โดยดูจากชนิดข้อมูล (ประเภท Class ของ object) ของ object นั้นๆ
  • Virtual Destructor เป็นตัวทำให้มั่นใจว่าในกรณีที่ object ถูกลบผ่านทาง pointer ที่ชี้ไปยัง base class destructor ของ derived class จะถูกเรียกใช้งาน ซึ่งจะช่วยป้องกัน memory leak ได้

สรุปหัวข้อทั้งหมด

และนี่คือตอนที่ 3 ของหัวข้อ C++ DSA หัวข้อที่เราเรียนในตอนนี้นั้นจะเป็นพื้นฐานทาง code ที่นำไปต่อยอดต่อกับหัวข้อต่อๆไปของ Data Strcuture ได้

Session ต่อไปเราจะเริ่มเข้าสู่ภาคทฤษฎีของ Data Structure กัน เราจะเริ่มมารู้จักข้อมูล Structure แต่ละแบบ การจัดการข้อมูลแต่ละประเภท ว่ามี รูปแบบไหน และมี use case ประมาณไหนบ้าง

เจอกันใน Session ต่อไปครับ