+1

[C++ OOP Thực Chiến] Bài 26: Getter - Setter là gì? - Nghệ thuật kiểm soát cổng ra vào dữ liệu

Chào anh em! Trong suốt các bài qua của project PhanSo, chúng ta đã giấu kỹ tuSomauSo vào khu vực private. Chúng ta dùng Constructor để khởi tạo ban đầu, dùng hàm friendoperator để tính toán.

Nhưng ở môi trường thực tế, giả sử Frontend gọi một API yêu cầu: "Cho tôi xin cái tử số để tôi in lên giao diện", hoặc "Người dùng vừa nhập mẫu số mới trên form, hãy cập nhật lại cho tôi". Bạn không thể lôi một hàm operator- ra để xử lý việc này được.

Chúng ta cần những "cánh cửa" hợp pháp để giao tiếp với thế giới bên ngoài. Đó chính là GetterSetter.

1. Bản chất của Getter và Setter

Trong OOP, dữ liệu (Thuộc tính) sinh ra là để được bảo vệ (private). Nhưng bảo vệ không có nghĩa là giam cầm mãi mãi.

  • Getter (Hàm lấy dữ liệu): Là cánh cửa cho phép thế giới bên ngoài ĐỌC giá trị của thuộc tính, nhưng không được phép sửa.
  • Setter (Hàm gán dữ liệu): Là cánh cửa cho phép thế giới bên ngoài GHI/CẬP NHẬT giá trị mới cho thuộc tính, nhưng phải đi qua sự kiểm duyệt gắt gao.

Nhiều bạn thắc mắc: "Thế tại sao không để luôn public cho người ta gán biến thẳng tay (ví dụ ps.mauSo = 5) mà phải đẻ ra hàm Setter (ps.setMauSo(5)) làm gì cho mệt?" Câu trả lời nằm ở hai chữ: Kiểm soát (Control).

2. Căn bệnh "Auto-Generate" của Junior

Khi dùng các IDE xịn, các bạn mới học rất thích bôi đen tất cả biến private rồi ấn tổ hợp phím để nó tự động đẻ ra Getter/Setter cho toàn bộ. Đây là một thảm họa thiết kế!

Nếu bạn thiết kế một Class Transaction (Giao dịch ngân hàng):

  • Transaction ID (Mã giao dịch): Chỉ được cấp 1 lần khi tạo. Bạn CHỈ được viết Getter (để đọc mã). TUYỆT ĐỐI KHÔNG viết Setter. Nếu có Setter, hacker sẽ dùng nó để sửa mã giao dịch của người khác!
  • Balance (Số dư): Không có Setter để gán thẳng số dư. Bạn phải dùng các hàm nghiệp vụ như deposit() (Nạp) và withdraw() (Rút) để kiểm soát dòng tiền như ta đã học ở Bài 4.

Quy tắc: Chỉ viết Getter/Setter cho những thuộc tính THỰC SỰ CẦN THIẾT.

3. Code Demo: Gắn "Lính gác" cho Phân số

Quay lại với Class PhanSo, nếu ai đó cố tình gán Mẫu số = 0, chương trình sẽ crash (lỗi chia cho 0). Hãy xem cách Setter bảo vệ hệ thống:

#include <iostream>
#include <cmath>

using namespace std;

class PhanSo {
private:
    int tuSo;
    int mauSo;

    void rutGon() {
        if (mauSo < 0) { tuSo = -tuSo; mauSo = -mauSo; }
        int a = abs(tuSo), b = abs(mauSo);
        while (b != 0) { int temp = b; b = a % b; a = temp; }
        int ucln = (a == 0) ? 1 : a;
        tuSo /= ucln; mauSo /= ucln;
    }

public:
    PhanSo(int tu = 0, int mau = 1) : tuSo(tu), mauSo(mau) { rutGon(); }

    void inPhanSo() const {
        if (mauSo == 1) cout << tuSo << "\n";
        else if (tuSo == 0) cout << "0\n";
        else cout << tuSo << "/" << mauSo << "\n";
    }

    // --- GETTER ---
    // BẮT BUỘC phải có chữ 'const' ở cuối, vì hàm đọc không được phép sửa data
    int getTuSo() const { return tuSo; }
    int getMauSo() const { return mauSo; }

    // --- SETTER ---
    void setTuSo(int tuMoi) {
        tuSo = tuMoi;
        rutGon(); // Đổi tử số xong phải rút gọn lại ngay!
    }

    void setMauSo(int mauMoi) {
        // LÍNH GÁC: Kiểm tra logic nghiệp vụ trước khi cho phép ghi đè
        if (mauMoi == 0) {
            cout << "[LỖI TỪ SETTER] Mau so khong the bang 0. Giu nguyen gia tri cu!\n";
            return; // Đuổi về, không cho cập nhật
        }
        mauSo = mauMoi;
        rutGon(); // Đổi mẫu số xong phải rút gọn lại ngay!
    }
};

int main() {
    cout << "--- HE THONG GETTER / SETTER ---\n";

    PhanSo ps(1, 2);
    
    // Sử dụng Getter để lấy dữ liệu in ra log
    cout << "Tu so hien tai: " << ps.getTuSo() << "\n";
    cout << "Mau so hien tai: " << ps.getMauSo() << "\n";

    // Cập nhật tử số an toàn
    cout << "\n[Action] Thay doi tu so thanh 4...\n";
    ps.setTuSo(4);
    cout << "-> Phan so moi: "; ps.inPhanSo(); // Tự động rút gọn 4/2 thành 2

    // Cố tình "phá hoại" bằng cách set mẫu số = 0
    cout << "\n[Action] Hacker co tinh set mau so = 0...\n";
    ps.setMauSo(0); // Lính gác sẽ chặn lại!
    
    cout << "-> Phan so sau khi bi pha: "; ps.inPhanSo(); // Vẫn giữ nguyên là 2

    return 0;
}

Nhận xét: Thay vì mở toang cửa public cho thiên hạ vào phá nát Mẫu số, hàm setMauSo đóng vai trò như một anh bảo vệ, kiểm tra thẻ (dữ liệu đầu vào) hợp lệ mới cho qua.

Tạm kết & Gợi mở

Anh em thấy đấy, Getter và Setter không hề nhàm chán nếu chúng ta biết đặt đúng logic nghiệp vụ vào bên trong nó. Nó giúp Class của chúng ta trở thành một pháo đài bất khả xâm phạm.

Và nhờ có sự xuất hiện của bộ đôi Getter/Setter này, chúng ta đã có thêm một cách mới để tương tác với Object từ bên ngoài mà không cần dùng đến quyền lực tối thượng của friend.

Nhớ lại ở Bài 25, chúng ta đã code Toán tử số đối -ps bằng Member Function (hàm nằm bên TRONG Class). Vậy nếu chúng ta muốn lôi cái hàm đó ra NGOÀI Class (trở thành Non-member function) thì sao? Chúng ta sẽ móc dữ liệu ra bằng cách nào khi không còn con trỏ this?

Hẹn gặp lại anh em ở Bài 27: Toán tử số đối dùng non-member function - Góc nhìn từ bên ngoài pháo đài. Đừng quên thả Upvote để tiếp lửa cho series nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí