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