Encapsulation ในภาษา C++
ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับคุณสมบัติการห่อหุ้ม (Encapsulation) ในภาษา C++ ซึ่งเป็นรูปแบบหนึ่งของการเขียนโปรแกรมที่นำมาใช้ในการเขียนโปรแกรมเชิงวัตถุ เราจะพูดถึงความหมายของ Encapsulation และการประยุกต์ใช้ในการเขียนโปรแกรม นี่เป็นเนื้อหาในบทนี้
- Encapsulation คืออะไร
- Access modifiers
- คำสั่ง private
- คำสั่ง public
- คำสั่ง protected
Encapsulation คืออะไร
Encapsulation หรือการห่อหุ่มข้อมูล คิือรูปแบบการเขียนโปรแกรมที่นำมาใช้ในการเขียนโปรแกรมเชิงวัตถุ (OOP) โดยมีแนวคิดในการผูกข้อมูลและเมธอดสำหรับจัดการข้อมูลดังกล่าวเข้าด้วยกัน ซึ่งมันสามารถช่วยให้การออกแบบโปรแกรมมีประสิทธิภาพ มีความปลอดภัย ลดความซับซ้อนของระบบ และง่ายต่อการพัฒนามากขึ้น
ลองจินตนาการว่าเมื่อคุณขับรถยนต์ คุณสามารถใช้งานรถยนต์จากฟังก์ชันที่มีให้เท่านั้น คุณกดปุ่ม Start เพื่อเปิดเครื่อง เหยียบคันเร่งเพื่อทำให้รถยนต์เคลื่อนไปข้างหน้า เหยียบเบรกเพื่อหยุดรถ เราเรียกฟังก์ชันเหล่านี้ว่า Public interfaces ซึ่งเป็นฟังก์ชันที่เราสามารถมีปฏิสัมพันธ์ได้โดยตรง
ในขณะเดียวเพื่อให้การทำงานของระบบสมบูรณ์ ยังมีการทำงานอื่นเบื้องหลังเกิดขึ้น เช่น การนำเชื้อเพลิงมาแปลงเป็นพลังงาน การทำให้ล้อของรถยนต์เคลื่อนที่ การต่อต้านของแผ่นเบรกหรือดรัมเบรกเพื่อทำให้ล้อหยุด ซึ่งฟังก์ชันเหล่านี้เป็นส่วนหนึ่งของการทำงานของรถยนต์ แต่มันถูกซ่อน (Encapsulated) จากไปเรา และเราเรียกว่า Private interfaces
นี่เป็นแนวคิดที่ถูกนำมาใช้ในการเขียนโปรแกรมเชิงวัตถุ เราสามารถใช้งานออบเจ็คโดยที่ไม่ต้องรู้การทำงานภายในของมัน แค่ทราบวิธีการใช้งานมันเท่านั้น เพื่อเรียนรู้และเข้าใจการทำงานของ Encapsulation เราจำเป็นจะต้องแนะนำให้คุณรู้จักกับ Access modifiers ซึ่งนี่เป็นสิ่งที่สำคัญเพื่อใช้ในการควบคุมว่าสิ่งต่างๆ ภายในคลาสจะสามารถถูกเข้าถึงได้อย่างไร
Access modifiers
Access modifiers หรือคำสั่งกำหนดระดับการเข้าถึง ใช้สำหรับควบคุมการเข้าถึงของตัวแปรหรือเมธอดภายในคลาสหรือจากภายนอกคลาส ในภาษา C++ นั้นมีคำสั่งกำหนดระดับการเข้าถึงอยู่สามระดับซึ่งสามารถแสดงได้ดังตารางต่อไปนี้
การเข้าถึง | public | protected | private |
---|---|---|---|
สมาชิกในคลาสเดียวกัน | ได้ | ได้ | ได้ |
สมาชิกของคลาสย่อย | ได้ | ได้ | ไม่ได้ |
จากภายนอกคลาส | ได้ | ไม่ได้ | ไม่ได้ |
คำสั่ง public
, protected
และ private
ที่คุณได้เห็นในตารางนั้นใช้สำหรับกำหนดระดับการเข้าถึงให้กับสมาชิกภายในคลาส เช่น ตัวแปรหรือเมธอด ซึ่งโดยปกติแล้วระดับการเข้าถึงของสมาชิกภายในคลาสจะเป็นแบบ private
เสมอ ซึ่งหมายถึงปิดกั้นมากที่สุด
ในตัวอย่างนี้่ เป็นการสร้างคลาสสำหรับออบเจ็คของรูปสี่เหลี่ยมเพื่อแสดงการทำงานของตัวกำหนดระดับการเข้าถึงในภาษา C++ เราจะเขียนโปรแกรมสำหรับหาพื้นที่ของรูปสี่เหลี่ยมจากความกว้างและความยาวของมัน
#include <iostream>
using namespace std;
class Rectangle {
private:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) { }
int area() {
return width * height;
}
};
int main() {
Rectangle rect(3, 4);
cout << "Area: " << rect.area() << endl;
return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Area: 12
ในตัวอย่างนี้ แสดงการทำงานของคำสั่งกำหนดระดับการเข้าถึงแบบ private
และ public
ในการประกาศสมาชิกภายในคลาส Rectangle
เราสามารถระบุระดับการเข้าถึงให้กับตัวแปรและเมธอดภายในคลาสได้โดยกำหนดคำสั่งกำหนดการเข้าถึงตามด้วยเครื่องหมายโคลอน (:
)
private:
int width;
int height;
เช่นเมื่อเราประกาศคำสั่งกำหนดการเข้าถึงเป็นแบบ private
สมาชิกที่ถูกประกาศหลังจากคำสั่งนี้จะมีระดับการเข้าถึงเป็นแบบ private
นั่นหมายความว่าตัวแปร width
และ height
จะสามารถเข้าถึงได้จากคลาสที่มันถูกประกาศเท่านั้น นั่นคือภายในคลาส Rectangle
นอกจากนี้ เรายังสามารถกำหนดระดับการเข้าถึงแบบอื่นๆ ภายในคลาสเดียวกันได้ โดยประกาศคำสั่งกำหนดการเข้าถึงและตามด้วยการประกาศสมาชิกของคลาสเช่นเดิม
public:
Rectangle(int w, int h) : width(w), height(h) { }
int area() {
return width * height;
}
ในกลุ่มของคำสั่งนี้เราได้กำหนดระดับการเข้าถึงเป็นแบบ public
นั่นหมายความว่าสมาชิกที่ถูกประกาศหลังจากนี้จะมีระดับการเข้าถึงแบบ public
ซึ่งมันจะสามารถเข้าถึงได้จากภายในคลาสและจากภายนอกของคลาส
ในตัวอย่าง เราได้ประกาศคอนสตรัคเตอร์และเมธอด area()
เป็นแบบ public
นี่จะทำให้เราสามารถใช้งานสมาชิกเหล่านี้จากภายนอกของคลาสได้ นั่นคือในฟังก์ชัน main()
Rectangle rect(3, 4);
cout << "Area: " << rect.area() << endl;
นี่เป็นวิธีที่สมาชิกแบบ public
ถูกเข้าถึงจากภายนอกของคลาส เราใช้คลาสคอนสรัตเตอร์สำหรับสร้างออบเจ็ค และเรียกใช้งานเมธอด area()
ด้วยตัวแปรออบเจ็ค rect
ซึ่งเกิดขึ้นภายนอกคลาส
คำสั่ง private (ค่าเริ่มต้นของคลาส)
โดยปกติแล้ว ถ้าเราไม่ได้กำหนดคำสั่งกำหนดระดับการเข้าถึงก่อนประกาศสมาชิกภายในคลาส ในภาษา C++ สมาชิกเหล่านั้นจะมีระดับการเข้าถึงเป็นแบบ private
อัตโนมัติ
ในตัวอย่างนี้ แสดงการสร้างคลาสสำหรับวงกลมที่มีเมธอดสำหรับหาพื้นที่และมีข้อมูลบางอย่างเป็นแบบ private
#include <iostream>
using namespace std;
class Circle {
int radius;
float area() {
return 3.14 * radius * radius;
}
public:
Circle(int r) : radius(r) { }
void showArea() {
cout << "Area: " << area() << endl;
}
};
int main() {
Circle c(3);
c.showArea();
return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Area: 28.26
ในตัวอย่างนี้ เป็นการแสดงการทำงานของระดับการเข้าถึงสมาชิกของคลาสในภาษา C++ เมื่อเราไม่ได้กำหนดการเข้าถึงค่าเริ่มต้นของสมาชิกที่ถูกประกาศภายในคลาสจะเป็นแบบ private
int radius;
float area() {
return 3.14 * radius * radius;
}
จะเห็นว่าในการประกาศตัวแปร radius
และเมธอด area()
ภายในคลาสนั้นเราไม่ได้ประกาศคำสั่งกำหนดระดับการเข้าถึงสำหรับสมาชิกเหล่านี้ก่อน ดังนั้นตัวแปรและเมธอดนี้จะมีระดับการเข้าถึงเป็นแบบ private
คือสามารถเข้าถึงได้จากภายในคลาส Circle
เท่านั้น
public:
Circle(int r) : radius(r) { }
void showArea() {
cout << "Area: " << area() << endl;
}
};
จากนั้นเราระบุคำสั่งกำหนดการเข้าถึงเป็นแบบ public
พร้อมกับประกาศคอนสตรัคเตอร์และเมธอด showArea()
สำหรับแสดงพื้นที่ของวงกลม โดยทั่วไปแล้วคอนสตรัคเตอร์จะถูกประกาศเป็นแบบ public
เสมอ เนื่องจากเราต้องใช้มันในการสร้างออบเจ็ค ซึ่งการสร้างนั้นเกิดขึ้นจากภายนอกคลาส
Circle c(3);
c.showArea();
เรานำคลาสของวงกลมมาสร้างออบเจ็ค และเรียกใช้งานเมธอด showArea()
สำหรับแสดงพื้นที่ของวงกลมออกทางหน้าจอ และอย่างที่คุณเห็น ภายนอกคลาสเราสามารถเรียกใช้ได้เพียงเมธอด showArea()
เท่านั้นเนื่องจากเมธอดนี้เป็นแบบ public
สำหรับเมธอด area()
ถูกประกาศเป็นแบบ private
นั่นหมายความว่าเราไม่สามารถเรียกใช้งานมันได้ผ่านตัวแปรออบเจ็ค c
ซึ่งอยู่ภายนอกคลาส แต่เมธอดนี้ยังสามารถเรียกใช้ผ่านเมธอด showArea()
ได้เนื่องจากมันอยู่ภายในคลาสเดียวกัน
ดังนั้นจะกล่าวได้ว่าเมธอด area()
นั้นถูกซ่อนจากภายนอกคลาสและถูกห่อหุ้มไว้ภายในคลาส แต่เราสามารถทราบพื้นที่ของวงกลมได้จากการเรียกใช้เมธอด showArea()
และเราสามารถเปลี่ยนระดับการเข้าถึงของเมธอด area()
ให้เป็นแบบ public
เพื่อให้มันสามารถเข้าถึงจากภายนอกคลาสโดยตรงได้
คำสั่ง public
เมื่อเรากำหนดระดับการเข้าถึงให้กับสมาชิกของคลาสเป็นแบบ public
จะทำให้มันสามารถเข้าถึงได้จากทุกที่ หรือกล่าวอีกนัยหนึี่งภายนอกคลาสหรือจากตัวแปรออบเจ็คของคลาสดังกล่าวนั่นเอง มาดูตัวอย่างการใช้งานคำสั่ง public
ในภาษา C++
ในตัวอย่างนี้ เราจะสร้างคลาสสำหรับเก็บข้อมูลของผู้ใช้ที่ประกอบไปด้วย ชื่อ นามสกุล และอีเมล เพื่อแสดงการทำงานของตัวกำหนดระดับการเข้าถึงแบบ public
#include <iostream>
using namespace std;
class User {
private:
string email;
public:
string firstName;
string lastName;
User(string f, string l, string e) :
firstName(f), lastName(l), email(e) { }
string fullname() {
return firstName + " " + lastName;
}
string getEmail() {
return email;
}
};
int main() {
User u("Matteo", "Crisco", "[email protected]");
cout << u.firstName << endl;
cout << u.lastName << endl;
cout << u.fullname() << endl;
cout << u.getEmail() << endl;
return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Matteo
Crisco
Matteo Crisco
[email protected]
ในตัวอย่างนี้ เป็นการใช้งานคำสั่งกำหนดระดับการเข้าถึงให้กับสมาชิกภายในคลาสแบบ public
เพื่อให้สมาชิกเหล่านั้นสามารถเข้าถึงจากภายนอกของคลาสได้
private:
string email;
เราได้สร้างคลาส User
ซึ่งมีหนึ่งสมาชิกที่เป็นแบบ private
นั่นคือตัวแปร email
และอย่างที่คุณรู้ตัวแปรนี้จะสามารถเข้าถึงได้จากภายในคลาสนี้เท่านั้น
public:
string firstName;
string lastName;
สิ่งใหม่ที่คุณได้เห็นในตัวอย่างนี้คือเราได้ประกาศตัวแปรภายให้มีระดับการเข้าถึงเป็นแบบ public
นั่นจะทำให้เราสามารถเข้าถึงตัวแปรเหล่านี้ได้จากภายนอกของคลาส นั่นคือผ่านออบเจ็คของคลาสได้โดยตรง แน่นอนว่าเราสามารถเข้าถึงมันสำหรับอ่านหรือเขียนข้อมูลก็ได้
User u("Matteo", "Crisco", "[email protected]");
cout << u.firstName << endl;
cout << u.lastName << endl;
จากนั้นเราได้นำคลาสไปสร้างออบเจ็ค และกำหนดค่าให้กับออบเจ็คผ่านคอนสตรัคเตอร์ของคลาส จะเห็นว่าเราสามารถเข้าถึงสมาชิกตัวแปร firstName
และ lastName
ของออบเจ็คได้โดยตรง นั่นเป็นเพราะว่าตัวแปรเหล่านี้มีระดับการเข้าถึงเป็นแบบ public
ที่เราได้กำหนดเอาไว้ภายในคลาสนั่นเอง
cout << u.fullname() << endl;
cout << u.getEmail() << endl;
เมธอด fullname()
ใช้สำหรับรับเอาชื่อในรูปแบบเต็มที่เป็นการเชื่อมต่อกันระหว่างชื่อและนามสกุล เนื่องจากสมาชิกแบบ public
สามารถเข้าถึงได้ภายในคลาสเดียวกันด้วย ดังนั้นเมธอด fullname()
สามารถที่จะเข้าถึงสองตัวแปรเหล่านี้ได้เช่นเดิม
สำหรับเมธอด getEmail()
นั้นใช้สำหรับเข้าถึงตัวแปร email
เพราะเราไม่สามารถเข้าถึงตัวแปรนี้โดยตรงได้เนื่องจากมันมีระดับการเข้าถึงเป็นแบบ private
ทางเดียวที่จะสามารถเข้าถึงมันได้คือต้องผ่านเมธอดที่เป็น public
มาดูอีกตัวอย่างสำหรับการกำหนดระดับการเข้าถึงเป็นแบบ public
ในตัวอย่างนี้เป็นโปรแกรมสำหรับคำนวณหาระยะห่างระหว่างจุดสองจุดในสองมิติ เราจะสร้างคลาส Point
โดยที่ตัวแปรสมาชิกของคลาสนั้นมีระดับการเข้าถึงเป็นแบบ public
#include <iostream>
#include <cmath>
using namespace std;
class Point {
public:
int x;
int y;
Point() {}
};
float distance(Point p1, Point p2) {
return sqrt(pow((p1.x - p1.y), 2) +
pow((p2.x - p2.y), 2));
}
int main() {
Point p1, p2;
p1.x = 0;
p1.y = 6;
p2.x = 4;
p2.y = -2;
cout << "Distance between ";
cout << "(" << p1.x << "," << p1.y << ") and ";
cout << "(" << p2.x << "," << p2.y << ") is ";
cout << distance(p1, p2) << endl;
return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Distance between (0,6) and (4,-2) is 8.48528
ในตัวอย่าง เป็นโปรแกรมสำหรับหาระยะห่างระหว่างจุดสองจุดในสองมิติ แต่สิ่งที่เราอยากแสดงให้คุณในตัวอย่างนี้คือการใช้งานคำสั่ง public
สำหรับกำหนดระดับการเข้าถึงให้กับสมาชิกภายในคลาส
public:
int x;
int y;
เราได้สร้างคลาส Point
ซึ่งเป็นคลาสของจุดในสองมิติที่เก็บตำแหน่งของจุดในพิกัดแกน x
และ y
เรากำหนดให้ตัวแปรเหล่านี้มีระดับการเข้าถึงเป็นแบบ public
Point p1, p2;
p1.x = 0;
p1.y = 6;
p2.x = 4;
p2.y = -2;
จากนั้นสร้างออบเจ็คของจุดสองจุดเพื่อนำมาหาระยะห่าง เนื่องจากสมาชิกภายในออบเจ็คนั้นเป็นแบบ public
เราสามารถกำหนดค่าพิกัดผ่านออบเจ็คได้โดยตรง โดยไม่จำเป็นต้องทำผ่านคอนสตรัคเตอร์
cout << "(" << p1.x << "," << p1.y << ") and ";
cout << "(" << p2.x << "," << p2.y << ") is ";
และเช่นเดียวกันในการอ่านค่าพิกัดมาแสดงผลเราสามารถเข้าถึงผ่านออบเจ็คได้โดยตรง โดยที่ไม่จำเป็นต้องทำผ่าน Getter เมธอด
float distance(Point p1, Point p2) {
return sqrt(pow((p1.x - p1.y), 2) +
pow((p2.x - p2.y), 2));
}
และภายในฟังก์ชัน distance()
เช่นเดียวกัน เราอ่านค่าพิกัดบนออบเจ็คของจุดทั้งสองสำหรับนำมาคำนวณหาระยะห่างและส่งค่าดังกล่าวกลับไป
จะเห็นว่าเมื่อเรากำหนดระดับการเข้าถึงของตัวแปรเป็นแบบ public
นั่นจะทำให้เราสามารถเข้าถึงสมาชิกเหล่าจากภายนอกออบเจ็คได้ ซึ่งการทำงานของมันจะเหมือนกับการใช้โครงสร้างข้อมูล (Struct)
อย่างไรก็ตาม ในการออกแบบคลาสแนะนำให้กำหนดระดับการเข้าถึงแบบ private
ถ้าหากไม่จำเป็นต้องเข้าถึงจากภายนอก นี่จะทำให้เราสามารถควบคุมวิธีการที่ข้อมูลจะถูกนำไปใช้งาน และลดปัญหาความซับซ้อนของโปรแกรมได้ โดยปกติแล้วเราใช้แนวคิดของ Getter และ Setter เมธอดสำหรับกำหนดค่าและรับค่าจากตัวแปรภายในออบเจ็ค
คำสั่ง protected
เมื่อสมาชิกภายในคลาสถูกกำหนดระดับการเข้าถึงเป็นแบบ protected
จะทำให้มันสามารถเข้าถึงได้จากภายในคลาสเดียวกันและคลาสที่สืบทอดคลาสดังกล่าวไปเท่านั้น ซึ่งนี่อาจมีประโยชน์เมื่อเราต้องการแบ่งปันสมาชิกระหว่างคลาสหลักและ Delivered คลาส แต่ไม่ใช่ภายนอกของคลาส นี่เป็นตัวอย่างการใช้งาน
ในตัวอย่างนี้ แสดงการทำงานของคลาสรูปสี่เหลี่ยมและคลาสลูกบาศก์ที่ต้องการแบ่งปันข้อมูลบางอย่างร่วมกัน โดยที่คลาสลูกบาศก์ต้องการใช้คุณสมบัติบางอย่างจากคลาสรูปสี่เหลี่ยมที่มันสืบทอดมาจาก
#include <iostream>
using namespace std;
class Rectangle {
protected:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) { }
int area() {
return width * height;
}
};
class Cube: public Rectangle {
private:
int dept;
public:
Cube(int w, int h, int d) :
Rectangle(w, h), dept(d) { }
int volume() {
return width * height * dept;
}
};
int main() {
Cube cube(3, 4, 5);
cout << "Volume: " << cube.volume() << endl;
return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Volume: 60
ในตัวอย่างนี้ แสดงให้คุณเห็นถึงการใช้งานของคำสั่ง protected
สำหรับกำหนดระดับการเข้าให้กับสมาชิกภายในคลาสให้สามารถเข้าถึงได้จากภายในคลาสที่สืบทอดและคลาสของตัวมันเอง
protected:
int width;
int height;
ในคลาส Rectangle
เราได้กำหนดระดับการเข้าถึงของตัวแปรและเมธอดเป็นแบบ protected
ที่จะทำให้เราสามารถเข้าถึงตัวแปรเหล่านี้ได้จากภายในคลาส Cube
ที่สืบทอดมาจากคลาสนี้ เนื่องจากคลาส Cube
นั้นก็จำเป็นต้องมีตัวแปรสำหรับเก็บความกว้างและความสูงเช่นกัน
int volume() {
return width * height * dept;
}
เมื่อคลาส Cube
ได้ทำการสืบทอดจากคลาส Rectangle
เราสามารถเข้าถึงสมาชิกที่เป็นแบบ protected
จากสมาชิกภายในคลาสนี้ได้โดยตรง ในตัวอย่างเมธอด volume()
เข้าถึงตัวแปรความกว้างและความสูงจากคลาสหลักเพื่อนำมาคำนวณหาปริมาตรสำหรับกล่องลูกบาศก์
สิ่งหนึ่งที่เหมือนกันของสองคลาสนี้คือเราไม่สามารถเข้าถึงตัวแปร width
, height
และ dept
จากภายนอกคลาสได้ ยกตัวอย่าเช่นในคำสั่งต่อไปนี้
Rectangle rect(3, 4);
rect.width = 1;
// Error: 'int Rectangle::width' is protected within this context
Cube cube(3, 4, 5);
cube.width = 1;
cube.dept = 3;
// Error: 'int Rectangle::width' is protected within this context
นี่จะทำให้เกิดข้อผิดพลาดขึ้นเพราะสมาชิกแบบ protected
จะสามารถเข้าถึงได้จากภายในคลาสเดียวกันหรือคลาสที่สืบทอดเท่านั้น เพื่อทำให้ตัวแปรเหล่านี้สามารถเข้าถึงได้จากภายนอกคลาส เราจะต้องกำหนดการเข้าถึงให้กับมันเป็นแบบ public
ในบทนี้ คุณได้เรียนรู้เกี่ยวกับ Encapsulation ในภาษา C++ สำหรับการควบคุมการเข้าถึงและปกปิดข้อมูลภายในคลาสให้สามารถเข้าถึงได้ตามที่กำหนดเท่านั้น โดยใช้ตัวควบคุมระดับการเข้าถึงทั้งสามระดับที่มีในภาษา C++ ได้แก่ private, protected และ public