Encapsulation ในภาษา C++

26 February 2021

ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับคุณสมบัติการห่อหุ้ม (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++ เราจะเขียนโปรแกรมสำหรับหาพื้นที่ของรูปสี่เหลี่ยมจากความกว้างและความยาวของมัน

rectangle.cpp
#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

default_private.cpp
#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

user.cpp
#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

distance.cpp
#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 คลาส แต่ไม่ใช่ภายนอกของคลาส นี่เป็นตัวอย่างการใช้งาน

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

cube.cpp
#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

บทความนี้เป็นประโยชน์หรือไม่?Yes·No