Bài tập

Tính trừu tượng (Abstraction) trong lập trình hướng đối tượng

Huy Erick

Tính trừu tượng (Abstraction) là một trong 4 tính chất đặc trưng quan trọng của lập trình hướng đối tượng (OOP - object-oriented programming). Mục tiêu chính của nó là làm giảm sự phức tạp...

Tính trừu tượng (Abstraction) là một trong 4 tính chất đặc trưng quan trọng của lập trình hướng đối tượng (OOP - object-oriented programming). Mục tiêu chính của nó là làm giảm sự phức tạp bằng cách ẩn các chi tiết không liên quan trực tiếp tới người dùng (người dùng ở đây không phải người dùng cuối mà là lập trình viên). Điều đó cho phép người dùng vẫn thực hiện được các công việc cần thiết dựa trên một thực thể trừu tượng được cung cấp mà không cần hiểu hoặc thậm chí không nghĩ về tất cả sự phức tạp ẩn giấu bên trong.

Tính trừu tượng không chỉ gói gọn trong lĩnh vực lập trình mà còn là một khái niệm chung được sử dụng trong cuộc sống hàng ngày.

Trừu tượng trong đời sống thực

Hãy tưởng tượng bạn là một người yêu thích cafe. Mỗi buổi sáng bạn đến công ty, sau khi ăn sáng, bạn luôn muốn uống một ly cafe sữa đá. Để có được ly cafe đó, bạn chỉ cần đến quầy bán cafe và gọi: "Em ơi, cho anh một ly cafe như mọi hôm (nhiều sữa ít đá)". Khoảng 2-3 phút sau đó, bạn đã có ngay một ly cafe trên tay mà không cần quan tâm nó được làm ra như thế nào. Bạn chỉ cần biết rằng bạn gọi một ly cafe, sau đó trả tiền và nhận cafe. Đó là một ví dụ về "trừu tượng" trong đời sống thực.

Một ví dụ khác là khi bạn gửi một email cho ai đó. Sau khi viết email xong, bạn chỉ cần nhấn nút "Gửi" và quan tâm vào việc gửi đi. Cái gì diễn ra sau khi bạn gửi email, dữ liệu được truyền như thế nào trên mạng cho đến khi đến được người nhận, bạn không quan tâm. Bạn chỉ cần biết rằng email đã được gửi đi. Đây là một ví dụ khác về "trừu tượng" trong đời sống thực.

Trừu tượng trong lập trình hướng đối tượng (OOP)

Trong lập trình hướng đối tượng, trừu tượng có thể chia thành 2 cảnh giới:

Cảnh giới thứ nhất: Trừu tượng dữ liệu và hàm

Dữ liệu và một số hàm không cần thiết đưa ra bên ngoài sẽ được đưa vào trong class và chỉ định truy cập là private (hoặc protected). Các dữ liệu và hàm đó sẽ không thể truy cập từ bên ngoài của class đó. Ở cảnh giới này, trừu tượng giúp cho code dễ hiểu hơn vì nó tập trung vào các tính năng cốt lõi mà không tập trung vào chi tiết nhỏ nhặt. Nó cũng giúp cho chương trình dễ bảo trì và hạn chế lỗi do truy cập dữ liệu không đúng cách. Ở level này, có thể coi "Abstraction = Encapsulation + Data Hiding".

Cảnh giới thứ hai: Trừu tượng từ giai đoạn design cho đến coding

Ở cảnh giới này, chúng ta thực hiện trừu tượng hoá từ giai đoạn design cho đến coding. Trong giai đoạn thiết kế, chúng ta tập trung vào "what" - công việc mà một module hoặc một class sẽ thực hiện mà không quan tâm đến "how" - cách thức thực hiện nó. Kết quả của giai đoạn thiết kế này là một cái gọi là interface. Các class hoặc module sẽ làm việc với nhau thông qua interface mà không cần biết cụ thể về nhau.

Trong C++, interface là một class chỉ chứa khai báo của hàm huỷ ảo (virtual destructor) và các hàm thuần ảo (pure virtual methods) khác. Trong giai đoạn coding, interface này sẽ được xây dựng cụ thể bằng các class khác, các class này kế thừa interface class và định nghĩa cụ thể các hàm thuần ảo đã được khai báo trong interface. Bằng cách này, các module hoặc class sẽ không phụ thuộc vào nhau mà chỉ phụ thuộc vào interface. Việc sửa code của các module hoặc class sẽ không kéo theo việc phải sửa code ở các module hoặc class khác.

Dưới đây là một ví dụ sơ sơ, đơn giản về interface:

#include <iostream>
using namespace std;

class IShape {
public:
    virtual void move_x() = 0;
    virtual void move_y() = 0;
    virtual void draw() = 0;
};

class Line : public IShape {
public:
    void move_x() override {
        cout << "Di chuyển trục x" << endl;
    }

    void move_y() override {
        cout << "Di chuyển trục y" << endl;
    }

    void draw() override {
        cout << "Vẽ đường" << endl;
    }
};

int main() {
    IShape* shape = new Line();
    shape->move_x();
    shape->move_y();
    shape->draw();
    delete shape;

    return 0;
}

Trong đoạn code trên, có một interface là IShape, và có một class Line thực hiện giao diện đó. Trong hàm main(), chúng ta chỉ cần sửa đoạn code mô phỏng từ dòng 24 đến 26 nếu muốn thay thế Line bằng Square. Việc này không ảnh hưởng đến phần code còn lại. Lợi ích của mô hình thiết kế kiểu này là:

  • Dễ dàng thay thế một class bằng class khác mà không ảnh hưởng đến các phần code khác.
  • Giảm sự phụ thuộc giữa các module hoặc class.

Nếu bạn mới tiếp cận lập trình hướng đối tượng, có thể sẽ khá khó hiểu về cảnh giới thứ hai này. Để hiểu thêm, bạn cần nắm vững khái niệm về lớp trừu tượng (Abstract Class) và tìm hiểu về các mô hình thiết kế như Abstract Factory. Nhưng đối với những ai mới học và làm C++, không hiểu về trừu tượng là chuyện bình thường. Qua thời gian, bạn sẽ hiểu rõ hơn về nó.

Tham khảo

Xem thêm

  • Tính đóng gói - Encapsulation
  • Tính kế thừa - Inheritance và đa hình - Polymorphism

— Phạm Minh Tuấn (Shun) —

1