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", "matteo@gmail.com");
    cout << u.firstName << endl;
    cout << u.lastName << endl;
    cout << u.fullname() << endl;
    cout << u.getEmail() << endl;
    return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Matteo
Crisco
Matteo Crisco
matteo@gmail.com
ในตัวอย่างนี้ เป็นการใช้งานคำสั่งกำหนดระดับการเข้าถึงให้กับสมาชิกภายในคลาสแบบ public เพื่อให้สมาชิกเหล่านั้นสามารถเข้าถึงจากภายนอกของคลาสได้
private:
    string email;
เราได้สร้างคลาส User ซึ่งมีหนึ่งสมาชิกที่เป็นแบบ private นั่นคือตัวแปร email และอย่างที่คุณรู้ตัวแปรนี้จะสามารถเข้าถึงได้จากภายในคลาสนี้เท่านั้น
public:
    string firstName;
    string lastName;
สิ่งใหม่ที่คุณได้เห็นในตัวอย่างนี้คือเราได้ประกาศตัวแปรภายให้มีระดับการเข้าถึงเป็นแบบ public นั่นจะทำให้เราสามารถเข้าถึงตัวแปรเหล่านี้ได้จากภายนอกของคลาส นั่นคือผ่านออบเจ็คของคลาสได้โดยตรง แน่นอนว่าเราสามารถเข้าถึงมันสำหรับอ่านหรือเขียนข้อมูลก็ได้
User u("Matteo", "Crisco", "matteo@gmail.com");
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