การสืบทอดคลาส ในภาษา C++

23 February 2021

การสืบทอดคลาส (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 ซึ่งเป็นคลาสของรูปทรง จากนั้นจะเป็นการสร้างคลาสใหม่โดยการสืบทอดมาจากคลาสนี้ นี่เป็นตัวอย่าง

Rectangle.cpp
#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 เพียงคลาสเดียวถูกใช้ประโยชน์ได้จากหลายคลาส ซึ่งมีประโยชน์เป็นอย่างมากในการนำโค้ดกลับมาใช้ซ้ำ นี่เป็นตัวอย่าง

Person.cpp
#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++ โดยการแสดงการทำงานกับคลาสของสัตว์และสุนัข

method_override.cpp
#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 เมธอด

base_class_method.cpp
#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 เมธอดสำหรับกำหนดการทำงานใหม่ให้กับเมธอดในคลาสย่อย

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