การสืบทอดคลาส ในภาษา C++
การสืบทอดคลาส (Inheritances) คือคุณสมบัติของการเขียนโปรแกรมเชิงวัตถุ (OOP) ที่เราสามารถสร้างคลาสใหม่โดยสืบทอดจากคลาสเดิมได้ โดยคลาสที่ได้รับการสืบทอดจะได้รับตัวแปรและเมธอดจากคลาสหลักมาใช้งาน และสามารถเพิ่มเติมการทำงานหรือความสามารถเข้าไปได้ นี่เป็นแนวคิดที่สำคัญของออกแบบโปรแกรมให้นำโค้ดกลับมาใช้ใหม่ได้ (Reusable)
ในบทนี้ คุณจะได้เรียนรู้การสืบทอดคลาสในภาษา C++ เราจะพูดถึงวิธีการประกาศคลาสเพื่อสืบทอดจากคลาสอื่น และคุณสมบัติอื่นๆ ที่สำคัญเกี่ยวกับการสืบทอด นี่เป็นเนื้อหาในบทนี้
- การสืบทอดคลาสในภาษา C++
 - การ Override เมธอด
 
การสืบทอดคลาสในภาษา C++
ในการทำความเข้าใจเกี่ยวกับการสืบทอดคลาสในภาษา C++ มีสองคำคัพท์ที่เราต้องนิยามและคุณต้องเข้าใจกับมันเนื่องจากเราจะใช้มันสำหรับคำอธิบายเนื้อหาในบทเรียนนี้
- Base class: คือคลาสหลักหรือคลาสที่จะถูกสืบทอดโดยคลาสอื่น
 - Delivered class: คลาสย่อยที่จะทำการสืบทอดจากคลาสหลัก หรือนำโค้ดจากคลาสอื่นมาใช้ และมันสามารถเพิ่มการทำงานหรือความสามารถของมันเข้าไปได้
 
หลังจากที่คุณได้รู้ความหมายของคำคัพท์ที่สำคัญเกี่ยวกับการสืบทอดคลาสไปแล้ว ต่อไปมาดูรูปแบบของการสืบทอดคลาสในภาษา C++ นี่เป็นรูปแบบการสืบทอดคลาสในภาษา C++
class BaseClass {
    ...
};
class DeriveredClass: public BaseClass {
    ...
};
ในรูปแบบด้านบน เป็นวิธีที่เราใช้สำหรับสืบทอดคลาสในภาษา C++ โดยที่ BaseClass เป็นคลาสๆ ใดที่ประกาศไว้ก่อนหน้า ส่วน DeriveredClass เป็นคลาสใหม่ที่เราต้องการสร้างขึ้นโดยการสืบทอดจาก BaseClass และเมื่อเราได้ทำการสืบทอดคลาส สิ่งต่อไปนี้จะถูกสืบทอดจาก BaseClass ไปยัง DeriveredClass
- Constructor และ Destructor ของคลาส
 - สมาชิกที่เป็นตัวแปรทั้งหมด (private, protected และ public)
 - เมธอดแบบ protected และ public
 - ฟังก์ชันแบบตัวดำเนินการกำหนดค่า 
= - ฟังก์ชัน friend
 
แม้ว่าสมาชิกที่เป็นตัวแปรแบบ private จะสืบทอดมายัง Delivered class แต่เราไม่สามารถเข้าถึงตัวแปรดังกล่าวภายในคลาสย่อยได้ เนื่องจากตัวแปรแบบ private สามารถเข้าถึงได้ภายในคลาสที่มันถูกประกาศเท่านั้น
ถ้าหากเราต้องการเข้าถึงตัวแปรภายใน Delivered class ระดับการเข้าถึงของตัวแปรจะต้องสูงกว่า private นั่นคือจะต้องเป็นแบบ protected หรือ public แทน
ต่อไปมาดูตัวอย่างของการสืบทอดคลาสในภาษา C++ เราจะมีคลาส Polygon ซึ่งเป็นคลาสของรูปทรง จากนั้นจะเป็นการสร้างคลาสใหม่โดยการสืบทอดมาจากคลาสนี้ นี่เป็นตัวอย่าง
#include <iostream>
using namespace std;
class Polygon
{
protected:
    int width, height;
public:
    Polygon(int w, int h)
    {
        width = w;
        height = h;
    }
};
class Rectangle: public Polygon
{
public:
    Rectangle (int w, int h) : Polygon(w,h) { }
    int area()
    {
        return width * height;
    }
};
int main()
{
    Rectangle rect(3, 4);
    cout << "Area: " << rect.area() << endl;
    return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Area: 12
ในตัวอย่าง เป็นการสืบทอดคลาสในภาษา C++ โดยเราได้สร้างคลาส Polygon คลาสนี้เป็นคลาสของรูปทรงที่มีสมาชิกเป็นตัวแปรที่มีระดับการเข้าถึงเป็นแบบ protected สองตัวแปรได้แก่ width และ height สำหรับเก็บความกว้างและความยาวของรูปทรงตามลำดับ
class Rectangle: public Polygon
{
public:
    Rectangle (int w, int h) : Polygon(w, h) { }
    int area()
    {
        return width * height;
    }
};
จากนั้นเราได้สร้างคลาสใหม่ที่ชื่อว่า Rectangle โดยการสืบทอดมาจากคลาส Polygon นี่จะทำให้คลาส Rectangle ได้รับการสืบทอดตัวแปรและเมธอดที่ประกาศไว้ในคลาสหลักทั้งหมด และสามารถใช้งานได้ในคลาสของมันเอง
Rectangle (int w, int h) : Polygon(w, h) { }
นอกจากนี้คลาส Rectangle ยังมีคอนสตรัคเตอร์และเมธอดเป็นของมันเอง คอนสตรัคเตอร์นั้นใช้สำหรับกำหนดค่าให้กับตัวแปรในตอนสร้างออบเจ็ค เนื่องจากสิ่งที่เราต้องการกำหนดคือความยาวและความสูงซึ่งมีอยู่แล้วในคอนสตรัคเตอร์ของคลาสหลัก ดังนั้นเราสามารถเรียกใช้คอนสตรัคเตอร์ของคลาสหลักได้ด้วยคำสั่ง Polygon(w, h)
int area()
{
    return width * height;
}
เมธอด area() เป็นเมธอดที่ใช้สำหรับหาพื้นที่ของรูปสี่เหลี่ยมจากความยาวและความสูงของออบเจ็ค โดยการกำหนดเมธอดเพิ่มเติมให้กับคลาส Rectangle ทำให้คลาสนี้มีความสามารถเพิ่มเติมขึ้นมาจากคลาสหลัก
Rectangle rect(3, 4);
cout << "Area: " << rect.area() << endl;
เมื่อคลาสได้ถูกประกาศเรียบร้อยแล้ว เราสามารถนำมันมาสร้างออบเจ็คได้เหมือนกับคลาสปกติ ในตัวอย่างเราได้สร้างออบเจ็คของคลาสรูปสี่เหลี่ยมโดยกำหนดความกว้างและความสูงเป็น 3 และ 4 ตามลำดับ จากนั้นเรียกใช้เมธอด area() เพื่อหาพื้นที่ของออบเจ็คดังกล่าว
ในตัวอย่างที่ผ่านมาคลาส Polygon ถูกสืบทอดไปยังคลาสเดียวซึ่งเป็นการแสดงตัวอย่างพื้นอย่างสำหรับการสืบทอด อย่างไรก็ตาม เมื่อคลาสถูกสร้างขึ้น เราสามารถใช้มันสำหรับสืบทอดไปยังคลาสใหม่อีกกี่คลาสก็ได้
ในตัวอย่างนี้ เราจะมีคลาส Person ซึ่งเป็นคลาสสำหรับบุคคล จากนั้นเราจะสร้างคลาสอื่นอีกสองคลาสเพื่อสืบทอดจากคลาสนี้ ในกรณีนี้จะทำให้คลาส Person เพียงคลาสเดียวถูกใช้ประโยชน์ได้จากหลายคลาส ซึ่งมีประโยชน์เป็นอย่างมากในการนำโค้ดกลับมาใช้ซ้ำ นี่เป็นตัวอย่าง
#include <iostream>
#include <string>
using namespace std;
class Person
{
private:
    string firstName, lastName;
public:
    Person(string a, string b)
    {
        firstName = a;
        lastName = b;
    }
    string fullName() {
        return firstName + " " + lastName;
    }
};
class Programmer: public Person
{
private:
    string language;
public:
    Programmer(string a, string b): Person(a, b) {}
    void setLanguage(string lang)
    {
        language = lang;
    }
    void info()
    {
        cout << "Name: " + fullName() << endl;
        cout << "Occupation: Programmer" << endl;
        cout << "Language: " + language << endl;
    }
};
class Musician: public Person
{
private:
    string genre;
public:
    Musician(string a, string b): Person(a, b) {}
    void setGenre(string gen)
    {
        genre = gen;
    }
    void info()
    {
        cout << "Name: " + fullName() << endl;
        cout << "Occupation: Musician" << endl;
        cout << "Genre: " + genre << endl;
    }
};
int main()
{
    // Create programmer object
    Programmer p ("Matteo", "Crisco");
    p.setLanguage("C++");
    // Create musician object
    Musician m ("Alan", "Walker");
    m.setGenre("EDM");
    // Display info
    p.info();
    m.info();
    return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
Name: Matteo Crisco
Occupation: Programmer
Language: C++
Name: Alan Walker
Occupation: Musician
Genre: EDM
ในตัวอย่างนี้ แสดงให้คุณเห็นถึงการออกแบบคลาสเพื่อให้สามารถนำกลับมาใช้ใหม่ได้อย่างมีประสิทธิภาพ ในโปรแกรมของเรานั้นได้ประกอบไปด้วย 3 คลาสดังต่อไปนี้
คลาส Person: เป็นคลาสของบุคคลที่ประกอบไปด้วยตัวแปรสำหรับเก็บชื่อและนามสกุล และมีเมธอด
fullName()สำหรับรับเอาชื่อแบบเต็มคลาส Programmer: เป็นคลาสของโปรแกรมเมอร์ และเนื่องจากคลาสนี้ต้องการมีข้อมูลเกี่ยวกับชื่อและนามสกุล มันไม่จำเป็นต้องประกาศขึ้นมาใหม่ แค่สืบทอดจากคลาส
Personซึ่งมีข้อมูลเหล่านี้อยู่แล้ว นอกจากนี้คลาสProgrammerยังมีตัวแปรและเมธอดเป็นของมันเอง สำหรับเก็บข้อมูลเกี่ยวกับภาษาเขียนโปรแกรมคลาส Musician: เป็นคลาสที่คล้ายกับคลาสโปรแกรมเมอร์ และมันเป็นคลาสสำหรับนักดนตรี และคลาส
Musicianก็มีตัวแปรและเมธอดเป็นของมันเอง สำหรับเก็บข้อมูลเกี่ยวกับประเภทของดนตรีที่เล่น และสืบทอดข้อมูลที่มีอยู่แล้วจากคลาสPersonเช่นเดียวกัน
จะเห็นว่าทั้งคลาส Programmer และ Musician มีบางอย่างที่เหมือนกันนั่นคือชื่อและนามสกุล เนื่องจากสิ่งเหล่านี้ซ้ำกัน ดังนั้นแทนที่จะเขียนมันไว้ในแต่ละคลาส เราสร้างคลาส Person สำหรับเก็บข้อมูลเพื่อให้คลาสทั้งสองนำไปสืบทอดและใช้งาน
นี่เป็นวิธีที่ดีในการออกแบบคลาส ทุกอย่างที่ซ้ำและมีแนวโน้มว่าจะสามารถใช้ซ้ำได้เราออกแบบให้มันอยู่ในคลาสหลักนั่นคือคลาส Person ส่วนคลาสที่สืบทอดจากคลาสนี้และเพิ่มข้อมูลเฉพาะเจาะจงของคลาสลงไปนั้นเรียกว่าคลาสย่อย เหมือนกับคลาส Programmer และ Musician
การ Override เมธอด
การ Override เมธอดคือการกำหนดการทำงานใหม่ให้กับเมธอดที่ Delivered คลาส โดยใช้ชื่อเมธอดเดิมจาก Base คลาส นี่จะทำให้เราสามารถกำหนดการทำงานที่ตรงกับคลาส Delivered มากขึ้นโดยที่ยังคงชื่อเดิมของเมธอดเอาไว้
นี่เป็นตัวอย่างของการ Override เมธอดในภาษา C++ โดยการแสดงการทำงานกับคลาสของสัตว์และสุนัข
#include <iostream>
using namespace std;
class Animal {
public:
    Animal() {}
    void move() {
        cout << "An animal is moving..." << endl;
    }
};
class Dog: public Animal {
public:
    Dog() {}
    void move() {
        cout << "A dog is running..." << endl;
    }
};
int main()
{
    Dog d;
    d.move();
    return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
A dog is running...
ในตัวอย่างนี้ แสดงวิธีการทำงานของการ Override เมธอดในภาษา C++ ในคลาส Animal มีหนึ่งเมธอด move() เมธอดนี้แสดงข้อความเกี่ยวกับการเคลื่อนที่ของสัตว์ จะเห็นว่าการทำงานของเมธอดนั้นเป็นแบบกว้างๆ ไม่เฉพาะเจาะจง
จากนั้นเราสร้างคลาส Dog โดยสืบทอดมาจากคลาส Animal ในคลาสนี้เราต้องการกำหนดการทำงานใหม่ให้กับเมธอด move() สำหรับคลาสสุนัขและยังคงต้องการใช้ชื่อเมธอดเดิม เพื่อทำเช่นนี้เราเพียงประกาศเมธอด move() ภายในคลาส Dog และกำหนดการทำงานใหม่ให้กับมัน
Dog d;
d.move();
จากนั้นเมื่อเราเรียกใช้เมธอด move() บนออบเจ็คของคลาส Dog การทำงานของโปรแกรมจะเป็นการทำงานของเมธอด move() ที่ถูกประกาศไว้ในคลาส Dog แทน จะสามารถกล่าวได้ว่าเมธอดนี้ได้ถูก Override โดยคลาส Dog เพื่อทำให้การทำงานสอดคล้องกับคลาสมากขึ้น
เมื่อไรก็ตามที่มีการ Override เมธอด เราจะไม่สามารถเข้าถึงเมธอดจาก Base คลาสที่ออบเจ็คของ Delivered คลาสได้อีกต่อไป ยกตัวอย่างเช่น คุณไม่สามารถเรียกใช้ d.move() เมื่อคาดหวังการทำงานของเมธอดจากคลาส Animal นี่เป็นวิธีการทำงานพื้นฐานของการ Override เมธอดในภาษา C++
แต่อย่างไรก็ตาม ในบางกรณีเราอาจยังคงต้องการเข้าถึงเมธอดจาก Base คลาส และยังคงต้องการ Override เมธอดดังกล่าวใน Delivered คลาส ซึ่งในการออกแบบคลาสบางครั้ง เมธอดของ Base คลาสที่ถูก Override ไปแล้ว อาจจะยังมีความจำเป็นสำหรับ Delivered คลาสอยู่
ในภาษา C++ แม้เราจะทำการ Override เมธอดของ Base คลาสจาก Delivered คลาสไปแล้ว แต่เรายังสามารถเข้าถึงเมธอดดังกล่าวของ Base คลาสได้อยู่ เราจะมาดูกันในตัวอย่างต่อไปนี้
นี่เป็นตัวอย่างนี้เป็นการออกแบบคลาส Driver สำหรับตัวเชื่อมต่อฐานข้อมูล โดยการใช้การสืบทอดและการ Override เมธอด
#include <iostream>
using namespace std;
class DbDriver {
public:
    void query(string name, string sql) {
        cout << "Perform low level query for " << name << endl;
    }
};
class MySqlDriver: public DbDriver {
public:
    void query(string sql) {
        cout << "MySQL: " << sql << endl;
        DbDriver::query("MySQL", sql);
    }
};
class PostgreSqlDriver: public DbDriver {
public:
    void query(string sql) {
        cout << "PostgreSQL: " << sql << endl;
        DbDriver::query("PostgreSQL", sql);
    }
};
int main()
{
    MySqlDriver mysql;
    mysql.query("select * from users;");
    PostgreSqlDriver postgresql;
    postgresql.query("select * from posts;");
    return 0;
}
นี่เป็นผลลัพธ์การทำงานของโปรแกรม
MySQL: select * from users;
Perform low level query for MySQL
PostgreSQL: select * from posts;
Perform low level query for PostgreSQL
ในตัวอย่างนี้ แสดงการใช้ประโยชน์จากการ Override เมธอดในขณะที่เรายังคงสามารถเข้าถึงเมธอดจาก Base คลาสได้ เราได้จำลองการสร้างคลาส Driver สำหรับคิวรี่ข้อมูลในฐานข้อมูล คลาส DbDriver เป็นคลาสหลักที่ประกอบไปด้วยเมธอด query() เมธอดนี้ถูกออกแบบสำหรับคิวรี่ข้อมูลจากฐานข้อมูลทุกชนิด
เราได้สร้างคลาส MySqlDriver และ PostgreSqlDriver สองคลาสนี้ได้สืบทอดมาจากคลาส DbDriver และแต่คลาสได้มีการกำหนดการทำงานเมธอด query() เป็นของมันเองสำหรับการทำงานที่แตกต่างกันออกไปในฐานข้อมูลแต่ละประเภท แต่ในตอนท้ายมันยังคงต้องการส่งต่อการทำงานต่อไปให้กับเมธอด query() จาก ฺBase คลาสด้วย
DbDriver::query("MySQL", sql);
ในภาษา C++ เหมือนกับที่คุณเห็นในเมธอด query() ของคลาส MySqlDriver และ PostgreSqlDriver เราสามารถเข้าถึงเมธอดจากคลาสหลักได้โดยการใช้ชื่อคลาสหลักตามด้วยเครื่องหมาย :: และชื่อของเมธอดที่ต้องการเรียกใช้งาน
นี่จะทำให้เรายังสามารถใช้งานเมธอดจากหลักได้ ถึงแม้เมธอดดังกล่าวจะถูก Override ไปแล้วในคลาสย่อย ซึ่งนี่เป็นตัวอย่างที่ใช้ในการเขียนโปรแกรมจริง เมื่อเราต้องการเปลี่ยนแปลงการทำงานของเมธอดในคลาสย่อย แต่ยังคงต้องการเข้าถึงการทำงานของเมธอดในคลาสหลักอยู่
ในบทนี้ คุณได้เรียนรู้เกี่ยวกับการสืบทอดคลาสในภาษา C++ ซึ่งเป็นคุณสมบัติที่สำคัญในการเขียนโปรแกรมเชิงวัตถุเพราะมันช่วยให้เรานำโค้ดจากคลาสที่มีอยู่แล้วกลับมาใช้ใหม่และเพิ่มเติมความสามารถเข้าไปได้ นอกจากนี้เรายังพูดถึงการ Override เมธอดสำหรับกำหนดการทำงานใหม่ให้กับเมธอดในคลาสย่อย