Kế thừa
Đặc trưng của OOP là khuyến khích tái sử dụng mã thông qua kế thừa. Một lớp mới được tạo ra từ lớp hiện có, được gọi là lớp cơ sở. Lớp dẫn xuất sử dụng các thành viên của lớp cơ sở, nhưng cũng có thể sửa đổi và bổ sung chúng.
Nhiều kiểu là biến thể của các kiểu hiện có. Việc phát triển mã mới cho từng kiểu thường rất tẻ nhạt. Ngoài ra, mã mới đồng nghĩa với lỗi mới. Lớp dẫn xuất kế thừa mô tả của lớp cơ sở, do đó không cần phải phát triển lại và kiểm tra lại mã. Các mối quan hệ kế thừa có tính phân cấp.
Phân cấp là một phương pháp cho phép sao chép các yếu tố trong tất cả sự đa dạng và phức tạp của chúng. Nó giới thiệu việc phân loại đối tượng. Ví dụ, bảng tuần hoàn các nguyên tố có các khí. Chúng sở hữu các thuộc tính vốn có của tất cả các nguyên tố tuần hoàn.
Các khí trơ tạo thành một tiểu lớp quan trọng tiếp theo. Phân cấp là khí trơ, như argon, là một loại khí, và khí, đến lượt nó, là một phần của hệ thống. Phân cấp này cho phép dễ dàng diễn giải hành vi của các khí trơ. Chúng ta biết rằng nguyên tử của chúng chứa proton và electron, điều này đúng với tất cả các nguyên tố khác.
Chúng ta biết rằng chúng ở trạng thái khí ở nhiệt độ phòng, giống như tất cả các khí khác. Chúng ta biết rằng không có khí nào trong tiểu lớp khí trơ tham gia phản ứng hóa học thông thường với các nguyên tố khác, và đó là đặc tính của tất cả các khí trơ.
Hãy xem xét một ví dụ về kế thừa của các hình dạng hình học. Để mô tả toàn bộ sự đa dạng của các hình dạng đơn giản (hình tròn, tam giác, hình chữ nhật, hình vuông, v.v.), cách tốt nhất là tạo một lớp cơ sở (ADT), là tổ tiên của tất cả các lớp dẫn xuất.
Hãy tạo một lớp cơ sở CShape, chỉ chứa các thành viên chung nhất mô tả hình dạng. Những thành viên này mô tả các thuộc tính đặc trưng của bất kỳ hình dạng nào - loại hình dạng và tọa độ điểm neo chính.
Ví dụ:
//--- Lớp cơ sở Shape
class CShape
{
protected:
int m_type; // Loại hình dạng
int m_xpos; // Tọa độ X của điểm cơ sở
int m_ypos; // Tọa độ Y của điểm cơ sở
public:
CShape(){m_type=`0`; m_xpos=`0`; m_ypos=`0`;} // Hàm tạo
void SetXPos(int x){m_xpos=x;} // Đặt X
void SetYPos(int y){m_ypos=y;} // Đặt Y
};
2
3
4
5
6
7
8
9
10
11
12
Tiếp theo, tạo các lớp mới dẫn xuất từ lớp cơ sở, trong đó chúng ta sẽ thêm các trường cần thiết, mỗi trường xác định một lớp cụ thể. Đối với hình dạng Hình tròn, cần thêm một thành viên chứa giá trị bán kính. Hình dạng Hình vuông được đặc trưng bởi giá trị cạnh. Do đó, các lớp dẫn xuất, kế thừa từ lớp cơ sở CShape, sẽ được khai báo như sau:
//--- Lớp dẫn xuất Hình tròn
class CCircle : `public` CShape // Sau dấu hai chấm, chúng ta định nghĩa lớp cơ sở
{ // từ đó thực hiện kế thừa
private:
int m_radius; // Bán kính hình tròn
public:
CCircle(){m_type=`1`;} // Hàm tạo, loại 1
};
2
3
4
5
6
7
8
Đối với khai báo lớp hình dạng Hình vuông cũng tương tự:
//--- Lớp dẫn xuất Hình vuông
class CSquare : `public` CShape // Sau dấu hai chấm, chúng ta định nghĩa lớp cơ sở
{ // từ đó thực hiện kế thừa
private:
int m_square_side; // Cạnh hình vuông
public:
CSquare(){m_type=`2`;} // Hàm tạo, loại 2
};
2
3
4
5
6
7
8
Cần lưu ý rằng khi một đối tượng được tạo, hàm tạo của lớp cơ sở được gọi trước, sau đó hàm tạo của lớp dẫn xuất được gọi. Khi một đối tượng bị hủy, trước tiên hàm hủy của lớp dẫn xuất được gọi, sau đó là hàm hủy của lớp cơ sở.
Do đó, bằng cách khai báo các thành viên chung nhất trong lớp cơ sở, chúng ta có thể thêm các thành viên bổ sung trong các lớp dẫn xuất, xác định một lớp cụ thể. Kế thừa cho phép tạo ra các thư viện mã mạnh mẽ có thể được tái sử dụng nhiều lần.
Cú pháp để tạo một lớp dẫn xuất từ một lớp hiện có như sau:
class class_name :
(`public` | `protected` | `private`) `opt` base_class_name
{
khai báo thành viên lớp
};
2
3
4
5
Một khía cạnh của lớp dẫn xuất là khả năng hiển thị (mở) của các thành viên kế thừa (người thừa kế). Các từ khóa public
, protected
và private
được sử dụng để chỉ ra mức độ mà các thành viên của lớp cơ sở sẽ khả dụng cho lớp dẫn xuất. Từ khóa public
sau dấu hai chấm trong tiêu đề của lớp dẫn xuất cho biết rằng các thành viên được bảo vệ và công khai của lớp cơ sở CShape nên được kế thừa dưới dạng các thành viên được bảo vệ và công khai của lớp dẫn xuất CCircle.
Các thành viên lớp riêng của lớp cơ sở không khả dụng cho lớp dẫn xuất. Kế thừa công khai cũng có nghĩa là các lớp dẫn xuất (CCircle và CSquare) là CShape. Nghĩa là, Hình vuông (CSquare) là một hình dạng (CShape), nhưng hình dạng không nhất thiết phải là hình vuông.
Lớp dẫn xuất là một sửa đổi của lớp cơ sở, nó kế thừa các thành viên được bảo vệ và công khai của lớp cơ sở. Các hàm tạo và hàm hủy của lớp cơ sở không thể được kế thừa. Ngoài các thành viên của lớp cơ sở, các thành viên mới được thêm vào trong lớp dẫn xuất.
Lớp dẫn xuất có thể bao gồm triển khai các hàm thành viên khác với lớp cơ sở. Điều này không liên quan gì đến nạp chồng, khi ý nghĩa của cùng một tên hàm có thể khác nhau đối với các chữ ký khác nhau.
Trong kế thừa được bảo vệ, các thành viên công khai và được bảo vệ của lớp cơ sở trở thành các thành viên được bảo vệ của lớp dẫn xuất. Trong kế thừa riêng, các thành viên công khai và được bảo vệ của lớp cơ sở trở thành các thành viên riêng của lớp dẫn xuất.
Trong kế thừa được bảo vệ và riêng, quan hệ "đối tượng của lớp dẫn xuất là đối tượng của lớp cơ sở" không đúng. Các loại kế thừa được bảo vệ và riêng rất hiếm, và mỗi loại cần được sử dụng cẩn thận.
Cần hiểu rằng loại kế thừa (public
, protected
hoặc private
) không ảnh hưởng đến cách truy cập các thành viên của lớp cơ sở trong hệ thống phân cấp kế thừa từ lớp dẫn xuất. Với bất kỳ loại kế thừa nào, chỉ các thành viên lớp cơ sở được khai báo với đặc tả truy cập public
và protected
sẽ khả dụng ngoài các lớp dẫn xuất. Hãy xem xét điều này trong ví dụ sau:
#property `copyright` `"Copyright 2000-2024, MetaQuotes Ltd."`
#property `link` `"https://www.mql5.com"`
#property `version` `"1.00"`
//+------------------------------------------------------------------+
//| Ví dụ lớp với một vài loại truy cập |
//+------------------------------------------------------------------+
class CBaseClass
{
private: //--- Thành viên riêng không khả dụng từ các lớp dẫn xuất
int m_member;
protected: //--- Phương thức được bảo vệ khả dụng từ lớp cơ sở và các lớp dẫn xuất của nó
int Member(){return(m_member);}
public: //--- Hàm tạo lớp khả dụng cho tất cả thành viên của các lớp
CBaseClass(){m_member=5;return;};
private: //--- Một phương thức riêng để gán giá trị cho m_member
void Member(int value) { m_member=value;};
};
//+------------------------------------------------------------------+
//| Lớp dẫn xuất với lỗi |
//+------------------------------------------------------------------+
class CDerived: `public` CBaseClass // Đặc tả kế thừa công khai có thể bỏ qua, vì nó là mặc định
{
public:
void Func() // Trong lớp dẫn xuất, định nghĩa một hàm với các lệnh gọi đến thành viên lớp cơ sở
{
//--- Một nỗ lực sửa đổi thành viên riêng của lớp cơ sở
m_member=0; // Lỗi, thành viên riêng của lớp cơ sở không khả dụng
Member(0); // Lỗi, phương thức riêng của lớp cơ sở không khả dụng trong các lớp dẫn xuất
//--- Đọc thành viên của lớp cơ sở
`Print`(m_member); // Lỗi, thành viên riêng của lớp cơ sở không khả dụng
`Print`(Member()); // Không lỗi, phương thức được bảo vệ khả dụng từ lớp cơ sở và các lớp dẫn xuất của nó
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Trong ví dụ trên, CBaseClass chỉ có một phương thức công khai – hàm tạo. Các hàm tạo được gọi tự động khi tạo một đối tượng lớp. Do đó, thành viên riêng m_member và các phương thức được bảo vệ Member() không thể được gọi từ bên ngoài. Nhưng trong trường hợp kế thừa công khai, phương thức Member() của lớp cơ sở sẽ khả dụng từ các lớp dẫn xuất.
Trong trường hợp kế thừa được bảo vệ, tất cả các thành viên của lớp cơ sở với quyền truy cập công khai và được bảo vệ trở thành được bảo vệ. Điều này có nghĩa là nếu các thành viên dữ liệu và phương thức công khai của lớp cơ sở có thể truy cập từ bên ngoài, với kế thừa được bảo vệ, chúng chỉ khả dụng từ các lớp của lớp dẫn xuất và các dẫn xuất tiếp theo của nó.
//+------------------------------------------------------------------+
//| Ví dụ lớp với một vài loại truy cập |
//+------------------------------------------------------------------+
class CBaseMathClass
{
private: //--- Thành viên riêng không khả dụng từ các lớp dẫn xuất
double m_Pi;
public: //--- Lấy và đặt giá trị cho m_Pi
void SetPI(double v){m_Pi=v;return;};
double GetPI(){return m_Pi;};
public: // Hàm tạo lớp khả dụng cho tất cả thành viên
CBaseMathClass() {SetPI(3.14); `PrintFormat`(`"%s"`,`__FUNCTION__`);};
};
//+------------------------------------------------------------------+
//| Lớp dẫn xuất, trong đó m_Pi không thể sửa đổi |
//+------------------------------------------------------------------+
class CProtectedChildClass: `protected` CBaseMathClass // Kế thừa được bảo vệ
{
private:
double m_radius;
public: //--- Các phương thức công khai trong lớp dẫn xuất
void SetRadius(double r){m_radius=r; return;};
double GetCircleLength(){return GetPI()*m_radius;}
};
//+------------------------------------------------------------------+
//| Hàm khởi động script |
//+------------------------------------------------------------------+
void `OnStart`()
{
//--- Khi tạo một lớp dẫn xuất, hàm tạo của lớp cơ sở sẽ được gọi tự động
CProtectedChildClass pt;
//--- Xác định bán kính
pt.SetRadius(10);
`PrintFormat`(`"Length=%G"`,pt.GetCircleLength());
//--- Nếu bỏ chú thích dòng dưới đây, chúng ta sẽ gặp lỗi ở giai đoạn biên dịch, vì SetPI() giờ là được bảo vệ
// pt.SetPI(3);
//--- Bây giờ khai báo một biến của lớp cơ sở và thử đặt hằng số Pi bằng 10
CBaseMathClass bc;
bc.SetPI(10);
//--- Đây là kết quả
`PrintFormat`(`"bc.GetPI()=%G"`,bc.GetPI());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Ví dụ cho thấy các phương thức SetPI() và GetPi() trong lớp cơ sở CBaseMathClass là mở và khả dụng để gọi từ bất kỳ nơi nào trong chương trình. Nhưng đồng thời, đối với CProtectedChildClass được dẫn xuất từ nó, các phương thức này chỉ có thể được gọi từ các phương thức của lớp CProtectedChildClass hoặc các lớp dẫn xuất của nó.
Trong trường hợp kế thừa riêng, tất cả các thành viên của lớp cơ sở với quyền truy cập công khai và được bảo vệ trở thành riêng, và việc gọi chúng trở nên không thể trong các kế thừa tiếp theo.
MQL5 không hỗ trợ kế thừa đa cấp.
Xem thêm