Lập trình C++ - Lớp cơ sở trừu tượng

Lớp cơ sở trừu tượng

Sự cho phép đa kế thừa trong C++ dẫn đến một số hậu quả xấu, đó là sự đụng độ giữa các thành phần của các lớp cơ sở, khi có ít nhất hai lớp cơ sở lại cùng được kế thừa từ một lớp cơ sở khác. 
Xét trường hợp: 

  • Lớp Bus kế thừa từ lớp Car và lớp PublicTransport. 
  • Nhưng lớp Car và lớp PublicTransport lại cùng được thừa kế từ lớp Engine (động cơ).
    Lớp Engine có một thuộc tính là power (công suất của động cơ). 

Khi đó, nảy sinh một số vấn đề như sau: 

  • Các thành phần dữ liệu của lớp Engine bị lặp lại trong lớp Bus hai lần: một lần do kế thừa theo đường Bus::Car::Engine, một lần theo đường Bus::PublicTransport::Engine. Điều này là không an toàn. 
  • Khi khai báo một đối tượng của lớp Bus, hàm khởi tạo của lớp Engine cũng được gọi hai lần: một lần do gọi truy hồi từ hàm khởi tạo lớp Car, một lần do gọi truy hồi từ hàm khởi tạo lớp PublicTransport. 
  • Khi giải phóng một đối tượng của lớp Bus, hàm huỷ bỏ của lớp Engine cũng sẽ bị gọi tới hai lần. 

Để tránh các vấn đề này, C++ cung cấp một khái niệm là kế thừa từ lớp cơ sở trừu tượng. Khi đó, ta cho các lớp Car và PublicTransport kế thừa trừu tượng từ lớp Engine. Bằng cách này, các thành phần của lớp Engine chỉ xuất hiện trong lớp Bus đúng một lần. Lớp Engine được gọi là lớp cơ sở trừu tượng của các lớp Car và PublicTransport. 
 


Khai báo lớp cơ sở trừu tượng 
Việc chỉ ra một sự kế thừa trừu tượng được thực hiện bằng từ khoá virtual khi khai báo lớp cơ sở: 

class <Tên lớp cơ sở>: <Từ khoá dẫn xuất> virtual <Tên lớp cơ sở>{ 
     …  // Khai báo các thành phần bổ sung 
}; 

Ví dụ: 
 

class Engine{ 
     …     // Các thành phần lớp Engine 
}; 
class Car: public virtual Engine{ 
         …  // Khai báo các thành phần bổ sung 
}; là khai báo lớp Car, kế thừa từ lớp cơ sở trừu tượng Engine, theo kiểu dẫn xuất public. 

Lưu ý: 

  • Từ khoá virtual được viết bằng chữ thường. 
  • Từ khoá virtual không ảnh hưởng đến phạm vi truy nhập thành phần lớp cơ sở, phạm vi này vẫn được quy định bởi từ khoá dẫn xuất như thông thường.  
  • Từ khoá virtual chỉ ra một lớp cơ sở là trừu tượng nhưng lại được viết trong khi khai báo lớp dẫn xuất. 
  •  Một lớp dẫn xuất có thể được kế thừa từ nhiều lớp cơ sở trừu tượng 

Hàm khởi tạo lớp cơ sở trừu tượng 
Khác với các lớp cơ sở thông thường, khi có một lớp dẫn xuất từ một lớp cơ sở trừu tượng, lại được lấy làm cơ sở cho một lớp dẫn xuất khác thì trong hàm khởi tạo của lớp dẫn xuất cuối cùng, vẫn phải gọi hàm khởi tạo tường minh của lớp cơ sở trừu tượng. Hơn nữa, hàm khởi tạo của lớp cơ sở trừu tượng phải được gọi sớm nhất. 
Ví dụ, khi lớp Car và lớp PublicTransport được kế thừa từ lớp cơ sở trừu tượng Engine. Sau đó, lớp Bus được kế thừa từ hai lớp Car và PublicTranport. Khi đó, hàm khởi tạo của lớp Bus cũng phải gọi tường minh hàm khởi tạo của lớp Engine, theo thứ tự sớm nhất, sau đó mới gọi đến hàm khởi tạo của các lớp Car và PublicTransport. 

class Engine{  	public: 
	 	 	Engine(){… }; 
}; 
class Car: public virtual Engine{  	 	//Lớp cơ sở virtual  	
     public: 
	 	 	Car(): Engine(){… }; 
}; 
class PublicTransport: public virtual Engine{ //Lớp cơ sở virtual  	
      public: 
	 	 	PublicTransport():Engine(){… }; 
}; 
class Bus: public Car, public PublicTransport{  	
    public: 
	 	 	// Gọi hàm khởi tạo tường minh của lớp cơ sở trừu tượng 
	   Bus():Engine(), Car(), PublicTransport(){… }; 
}; 

Lưu ý: 

  • Trong trường hợp lớp Engine không phải là lớp cơ sở trừu tượng của các lớp Car và PublicTransport, thì trong hàm khởi tạo của lớp Bus không cần gọi hàm khởi tạo của lớp Engine, mà chỉ cần gọi tới các hàm khởi tạo của các lớp cơ sở trực tiếp của lớp Bus là lớp Car và lớp PublicTransport. 

Ví dụ minh hoạ việc khai báo và sử dụng lớp cơ sở trừu tượng: lớp Engine là lớp cơ sở trừu tượng của các lớp Car và lớp PublicTransport. Hai lớp này, sau đó, lại làm lớp cơ sở của lớp Bus.  

#include<string> 
#include <iostream>
using namespace std;
/* Định nghĩa lớp Engine */
class Engine {
	int  power; 	 	 	// Công suất 
public:
	Engine() { power = 0; };  	// Khởi tạo không tham số  
	Engine(int power) {
		this->power = power;
	};
	void show(); 	 	 	// Giới thiệu  
	float getPower() {
		return power;
	};
};

// Giới thiệu 
void Engine::show() {
	cout << "This is an engine having a power of " << power << "KWH" << endl;

}

/* Định nghĩa lớp Car dẫn xuất từ lớp cơ sở trừu tượng Engine*/
class Car : public virtual Engine {
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car();
	Car(int speed, string mark, float price);
	void show(); 	 	 	// Giới thiệu  
	float getSpeed() {
		return speed;
	};
	string getMark() {
		return mark;
	};
	float getPrice() {
		return price;
	};
};
/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	this->speed = 0;
	this->mark = "";
	this->price = 0;
}
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}

/* Định nghĩa lớp PublicTransport dẫn xuất trừu tượng từ lớp Engine */
class PublicTransport : public virtual Engine {
	float ticket; 	 	 	// Giá vé phương tiện
public:
	PublicTransport(); 	 	// Khởi tạo không tham số 
	PublicTransport(int, float); // Khởi tạo đủ tham số
	void show(); 	 	 	// Giới thiệu 
	float getTicket() {
		return ticket;
	};
};

PublicTransport::PublicTransport() {  	// Khởi tạo không tham số  	
	this->ticket = 0;
}

// Khởi tạo đủ tham số 
PublicTransport::PublicTransport(int power, float ticket) : Engine(power) {
	this->ticket = ticket;
}

// Giới thiệu 
void PublicTransport::show() {
	cout << "This is a public transport havìn a ticket of $" << ticket << " and its power is " << getPower()
		<< "KWh" << endl;
}
/* Định nghĩa lớp Bus kế thừa từ lớp Car và PublicTransport */
class Bus : public Car, public PublicTransport { // Thứ tự khai báo  	 	
	int label;  	 	 	// Số hiệu tuyến xe  	
public:
	Bus(); 	 	 	 	// Khởi tạo không tham số  
	Bus(int, int, string, float, float, int);// Khởi tạo đủ tham số  	 	
	void show(); 	 	 	// Giới thiệu 
};

// Khởi tạo không tham số 
Bus::Bus() : Engine(), Car(), PublicTransport() {  	// Theo thứ tự dẫn xuất  	
	label = 0;
}

// Khởi tạo đủ tham số 
Bus::Bus(int power, int speed, string mark, float price, float ticket, int label) :
	Engine(power), Car(speed, mark, price), PublicTransport(power, ticket) {
	this->label = label;
}

// Giới thiệu 
void Bus::show() {
	cout << "This is a bus on the line " << label
		<< ", its speed is " << getSpeed()
		<< "km / h, power is" << Car::getPower()
		<< "KWh, mark is " << getMark() << ", price is $" << getPrice()
		<< " and ticket is " << getTicket() << endl;

}

// phương thức main 
int main() {
	Bus myBus(250, 100, "Mercedes", 3000, 1.5, 27);
	myBus.Car::Engine::show();  	// Hàm của lớp Engine 
	myBus.PublicTransport::Engine::show();// Hàm của lớp Engine 
	myBus.Car::show(); 	 	 	// Hàm của lớp Car 
	myBus.PublicTransport::show(); 	// Hàm của lớp PublicTransport 
	myBus.show(); 	 	 	 	// Hàm của lớp Bus 
	system("pause");
	return 0;
}

Kết quả:
 

This is an engine having a power of 250KWH
This is an engine having a power of 250KWH
This is a Mercedes having a speed of 100km / h and its price is $3000
This is a public transport hav∞n a ticket of $1.5 and its power is 250KWh
This is a bus on the line 27, its speed is 100km / h, power is250KWh, mark is Mercedes, price is $3000 and ticket is 1.5

Hai dòng đầu là kết quả của phương thức show() của lớp Engine: một lần gọi qua lớp Car, một lần gọi qua lớp PublicTransport, chúng cho kết quả như nhau. Dòng thứ ba là kết quả phương thức show() của lớp Car. Dòng thứ tư, tương ứng là kết quả phương thức show() của lớp PublicTransport. Dòng thứ năm là kết quả phương thức show() của lớp Bus.