Tách khai báo và định nghĩa lớp
Trong các dự án phần mềm lớn, việc tách các lớp thành một mô tả ngắn gọn (khai báo) và một định nghĩa bao gồm các chi tiết triển khai chính là rất tiện lợi. Trong một số trường hợp, sự tách biệt như vậy trở nên cần thiết nếu các lớp có cách nào đó tham chiếu lẫn nhau, tức là không lớp nào có thể được định nghĩa đầy đủ mà không có khai báo trước.
Chúng ta đã thấy một ví dụ về khai báo trước trong phần Các con trỏ (xem tệp ThisCallback.mq5
), nơi các lớp Manager
và Element
chứa các con trỏ lẫn nhau. Ở đó, lớp được khai báo trước dưới dạng ngắn gọn: dưới dạng tiêu đề với từ khóa class
và tên:
class Manager;
Tuy nhiên, đây là khai báo ngắn nhất có thể. Nó chỉ đăng ký tên và cho phép trì hoãn việc mô tả giao diện lập trình cho đến một thời điểm nào đó, nhưng mô tả này phải xuất hiện ở đâu đó sau trong mã.
Thông thường hơn, khai báo bao gồm một mô tả đầy đủ về giao diện: nó chỉ định tất cả các biến và tiêu đề phương thức của lớp nhưng không có phần thân (khối mã).
Các định nghĩa phương thức được viết riêng: với các tiêu đề sử dụng tên đầy đủ bao gồm tên của lớp (hoặc nhiều lớp và không gian tên nếu ngữ cảnh phương thức được lồng sâu). Tên của tất cả các lớp và tên phương thức được nối với nhau bằng toán tử chọn ngữ cảnh ::
.
type class_name [:: nested_class_name...] :: method_name([parameters...])
{
}
2
3
Về lý thuyết, bạn có thể định nghĩa một phần các phương thức trực tiếp trong khối mô tả lớp (thường làm điều này với các hàm nhỏ), và một số có thể được tách ra riêng (thường là các hàm lớn). Nhưng một phương thức chỉ được có một định nghĩa (tức là bạn không thể định nghĩa một phương thức trong khối lớp, rồi lại định nghĩa lần nữa ở bên ngoài) và một khai báo (định nghĩa trong khối lớp cũng là một khai báo).
Danh sách tham số, kiểu trả về và các bộ sửa đổi const
(nếu có) phải khớp chính xác trong khai báo và định nghĩa phương thức.
Hãy xem cách chúng ta có thể tách mô tả và định nghĩa của các lớp từ script ThisCallback.mq5
(một ví dụ từ phần Con trỏ): chúng ta sẽ tạo một bản tương tự với tên ThisCallback2.mq5
.
Khai báo trước Manager
vẫn sẽ đứng ở đầu. Tiếp theo, cả hai lớp Element
và Manager
được khai báo mà không có triển khai: thay vì khối mã với phần thân phương thức, có một dấu chấm phẩy.
class Manager; // thông báo sơ bộ
class Element
{
Manager *owner; // con trỏ
public:
Element(Manager &t);
void doMath();
string getMyName() const;
};
class Manager
{
Element *elements[1]; // mảng con trỏ (thay bằng động)
public:
~Manager();
Element *addElement();
void progressNotify(Element *e, const float percent);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Phần thứ hai của mã nguồn chứa triển khai của tất cả các phương thức (bản thân các triển khai không thay đổi).
Element::Element(Manager &t) : owner(&t)
{
}
void Element::doMath()
{
...
}
string Element::getMyName() const
{
return typename(this);
}
Manager::~Manager()
{
...
}
Element *Manager::addElement()
{
...
}
void Manager::progressNotify(Element *e, const float percent)
{
...
}
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
Các cấu trúc cũng hỗ trợ khai báo và định nghĩa phương thức riêng biệt.
Lưu ý rằng danh sách khởi tạo của hàm tạo (sau tên và :
) là một phần của định nghĩa và do đó phải đứng trước phần thân hàm (nói cách khác, danh sách khởi tạo không được phép trong khai báo hàm tạo nơi chỉ có tiêu đề).
Việc viết riêng khai báo và định nghĩa cho phép phát triển thư viện, mã nguồn của chúng phải được đóng. Trong trường hợp này, các khai báo được đặt trong một tệp tiêu đề riêng với phần mở rộng mqh
, trong khi các định nghĩa được đặt trong một tệp cùng tên với phần mở rộng mq5
. Chương trình được biên dịch và phân phối dưới dạng tệp ex5 kèm theo tệp tiêu đề mô tả giao diện bên ngoài.
Trong trường hợp này, có thể nảy sinh câu hỏi tại sao một phần của triển khai nội bộ, đặc biệt là cách tổ chức dữ liệu (các biến), lại hiển thị trong giao diện bên ngoài. Nói một cách nghiêm ngặt, điều này cho thấy mức độ trừu tượng chưa đủ trong hệ thống phân cấp lớp. Tất cả các lớp cung cấp giao diện bên ngoài không nên tiết lộ bất kỳ chi tiết triển khai nào.
Nói cách khác, nếu chúng ta đặt mục tiêu xuất các lớp trên từ một thư viện nhất định, thì chúng ta sẽ cần tách các phương thức của chúng thành các lớp cơ sở cung cấp mô tả API (không có trường dữ liệu), và Manager
cùng Element
kế thừa từ chúng. Đồng thời, trong các phương thức của lớp cơ sở, chúng ta không thể sử dụng bất kỳ dữ liệu nào từ các lớp dẫn xuất và, về cơ bản, chúng không thể có triển khai. Điều này có thể thực hiện được như thế nào?
Để làm điều này, có công nghệ về các phương thức trừu tượng, lớp trừu tượng và giao diện.