Lớp trừu tượng và giao diện
Để khám phá các lớp trừu tượng và giao diện, hãy quay lại ví dụ chương trình vẽ xuyên suốt của chúng ta. API của nó, để đơn giản, bao gồm một phương thức ảo duy nhất draw
. Cho đến nay, nó vẫn trống, nhưng ngay cả một triển khai trống như vậy cũng là một triển khai cụ thể. Tuy nhiên, các đối tượng của lớp Shape
không thể được vẽ - hình dạng của chúng không được xác định. Do đó, việc biến phương thức draw
thành trừu tượng, hay còn gọi là thuần ảo, là điều hợp lý.
Để làm điều này, khối với triển khai trống cần được loại bỏ và thêm "= 0" vào tiêu đề phương thức:
class Shape
{
public:
virtual void draw() = 0;
...
};
2
3
4
5
6
Một lớp có ít nhất một phương thức trừu tượng cũng trở thành trừu tượng, vì đối tượng của nó không thể được tạo ra: không có triển khai. Cụ thể, hàm tạo Shape
của chúng ta trước đây có thể được các lớp dẫn xuất sử dụng (nhờ bộ sửa đổi protected
), và các nhà phát triển của chúng, về lý thuyết, có thể tạo một đối tượng Shape
. Nhưng điều đó chỉ xảy ra trước đây, và sau khi khai báo phương thức trừu tượng, chúng ta đã chặn hành vi này, vì điều đó bị chúng ta, những tác giả của giao diện vẽ, cấm. Trình biên dịch sẽ báo lỗi:
'Shape' - cannot instantiate abstract class
'void Shape::draw()' is abstract
2
Cách tiếp cận tốt nhất để mô tả một giao diện là tạo một lớp trừu tượng cho nó, chỉ chứa các phương thức trừu tượng. Trong trường hợp của chúng ta, phương thức draw
nên được chuyển sang lớp mới Drawable
, và lớp Shape
nên kế thừa từ nó (Shapes.mq5
).
class Drawable
{
public:
virtual void draw() = 0;
};
class Shape : public Drawable
{
public:
...
// virtual void draw() = 0; // đã chuyển sang lớp cơ sở
...
};
2
3
4
5
6
7
8
9
10
11
12
13
Tất nhiên, các phương thức giao diện phải nằm trong phần public
.
MQL5 cung cấp một cách tiện lợi khác để mô tả giao diện bằng từ khóa interface
. Tất cả các phương thức trong một giao diện được khai báo mà không có triển khai và được coi là công khai và ảo. Mô tả của giao diện Drawable
, tương đương với lớp trên, trông như sau:
interface Drawable
{
void draw();
};
2
3
4
Trong trường hợp này, không cần thay đổi gì trong các lớp dẫn xuất nếu không có trường nào trong lớp trừu tượng (điều này sẽ vi phạm nguyên tắc trừu tượng).
Bây giờ là lúc để mở rộng giao diện và đưa bộ ba phương thức setColor
, moveX
, moveY
cũng trở thành một phần của nó.
interface Drawable
{
void draw();
Drawable *setColor(const color c);
Drawable *moveX(const int x);
Drawable *moveY(const int y);
};
2
3
4
5
6
7
Lưu ý rằng các phương thức trả về một đối tượng Drawable
vì tôi không biết gì về Shape
. Trong lớp Shape
, chúng ta đã có các triển khai phù hợp để ghi đè các phương thức này, vì Shape
kế thừa từ Drawable
(Shape
"giống như" các đối tượng Drawable
).
Giờ đây, các nhà phát triển bên thứ ba có thể thêm các họ lớp Drawable
khác vào chương trình vẽ, không chỉ là các hình dạng, mà còn văn bản, bitmap, và điều đáng kinh ngạc là các bộ sưu tập của các Drawable
khác, cho phép lồng các đối tượng vào nhau và tạo ra các bố cục phức tạp. Chỉ cần kế thừa từ giao diện và triển khai các phương thức của nó là đủ.
class Text : public Drawable
{
public:
Text(const string label)
{
...
}
void draw()
{
...
}
Text *setColor(const color c)
{
...
return &this;
}
...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Nếu các lớp hình dạng được phân phối dưới dạng thư viện nhị phân ex5 (không có mã nguồn), chúng ta sẽ cung cấp một tệp tiêu đề cho nó chỉ chứa mô tả giao diện, không có bất kỳ gợi ý nào về cấu trúc dữ liệu nội bộ.
Vì các hàm ảo được liên kết động (muộn) với một đối tượng trong quá trình thực thi chương trình, có thể xảy ra lỗi nghiêm trọng "Pure virtual function call": chương trình sẽ kết thúc. Điều này xảy ra nếu lập trình viên vô tình "quên" cung cấp triển khai. Trình biên dịch không phải lúc nào cũng có thể phát hiện những thiếu sót như vậy tại thời điểm biên dịch.