Menu
CoddyTech

Trường được xuất và không được xuất

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 47 trên 107.

Trong Go, tính đóng gói ở cấp độ struct phụ thuộc vào chữ hoa hay chữ thường trong tên trường. Quy tắc đơn giản này quyết định liệu bạn có thể truy cập các trường từ bên ngoài package định nghĩa struct hay không.

Các trường bắt đầu bằng chữ cái viết hoa sẽ được xuất: có thể truy cập từ bất kỳ package nào. Các trường bắt đầu bằng chữ cái viết thường sẽ không được xuất: chỉ có thể truy cập trong cùng một package:

// Trong package "user"
type User struct {
    Name     string  // Được xuất, có thể truy cập ở mọi nơi
    Email    string  // Được xuất, có thể truy cập ở mọi nơi
    password string  // Không được xuất, chỉ có thể truy cập trong package "user"
    age      int     // Không được xuất, chỉ có thể truy cập trong package "user"
}

Khi một package khác nhập và sử dụng struct này, nó chỉ có thể tương tác với các trường được xuất:

// Trong package "main"
import "user"

func main() {
    u := user.User{
        Name:  "Alice",      // chạy được
        Email: "a@mail.com", // chạy được
        // password: "secret", // lỗi biên dịch: trường không xác định
    }
    
    fmt.Println(u.Name)     // chạy được
    // fmt.Println(u.password) // lỗi biên dịch: không thể truy cập trường không được xuất
}

Cơ chế này bảo vệ dữ liệu nhạy cảm và chi tiết triển khai nội bộ. Trường password không thể được đọc hoặc sửa đổi trực tiếp từ bên ngoài package, buộc các đoạn code khác phải dùng những phương thức bạn cung cấp. Đây là cách Go thực hiện ẩn dữ liệu, một nguyên tắc cốt lõi của tính đóng gói trong lập trình hướng đối tượng.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống tài khoản ngân hàng minh họa tính đóng gói thông qua các trường được xuất và không được xuất. Bạn sẽ tạo một package bảo vệ dữ liệu tài chính nhạy cảm, đồng thời cung cấp thông tin công khai an toàn.

Bạn sẽ sắp xếp code trong hai tệp:

  • account.go: Tạo một struct BankAccount trong package main, mô phỏng một tài khoản ngân hàng thực với cơ chế bảo vệ dữ liệu phù hợp:
    • HolderName (được xuất) - tên chủ tài khoản, an toàn để hiển thị công khai
    • AccountType (được xuất) - loại tài khoản (ví dụ: "Savings", "Checking")
    • balance (không được xuất) - số dư thực tế, cần được bảo vệ
    • pin (không được xuất) - mã PIN tài khoản, dữ liệu rất nhạy cảm
    Thêm hàm khởi tạo NewBankAccount nhận tên chủ tài khoản, loại tài khoản, số dư ban đầu và mã PIN, sau đó trả về con trỏ đến một BankAccount mới. Đồng thời thêm phương thức GetPublicInfo() string trả về theo định dạng: Account: [HolderName] ([AccountType])
  • main.go: Đọc thông tin tài khoản từ đầu vào, tạo một BankAccount bằng hàm khởi tạo và minh họa tính đóng gói. In thông tin công khai bằng phương thức, sau đó truy cập trực tiếp và in từng trường được xuất trên các dòng riêng. Các trường không được xuất (balance và pin) được bảo vệ. Bạn chỉ có thể thiết lập chúng thông qua hàm khởi tạo.

Đầu vào sẽ gồm các dòng sau:

  • Dòng 1: Tên chủ tài khoản
  • Dòng 2: Loại tài khoản
  • Dòng 3: Số dư ban đầu (dạng số thực)
  • Dòng 4: Mã PIN

Ví dụ, với Alice Johnson, Savings, 5000.50 và 1234, đầu ra của bạn sẽ là:

Account: Alice Johnson (Savings)
Holder: Alice Johnson
Type: Savings

Lưu ý rằng số dư và mã PIN không bao giờ xuất hiện trong đầu ra: chúng được ẩn an toàn bên trong struct và chỉ có thể truy cập trong package. Đây chính là tính đóng gói: dữ liệu nhạy cảm được bảo vệ, trong khi thông tin công khai vẫn có thể truy cập.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc tên chủ tài khoản
	holderName, _ := reader.ReadString('\n')
	holderName = strings.TrimSpace(holderName)

	// Đọc loại tài khoản
	accountType, _ := reader.ReadString('\n')
	accountType = strings.TrimSpace(accountType)

	// Đọc số dư ban đầu
	balanceStr, _ := reader.ReadString('\n')
	balanceStr = strings.TrimSpace(balanceStr)
	balance, _ := strconv.ParseFloat(balanceStr, 64)

	// Đọc PIN
	pinStr, _ := reader.ReadString('\n')
	pin := strings.TrimSpace(pinStr)

	// TODO: Tạo BankAccount mới bằng hàm khởi tạo
	// account := NewBankAccount(...)

	// TODO: In ra thông tin công khai bằng phương thức GetPublicInfo()

	// TODO: In ra tên chủ tài khoản bằng cách truy cập trực tiếp trường được xuất
	// fmt.Println("Holder:", ...)

	// TODO: In ra loại tài khoản bằng cách truy cập trực tiếp trường được xuất
	// fmt.Println("Type:", ...)

	// Lưu ý: balance và pin không được xuất nên không thể truy cập trực tiếp ở đây
	// Chúng được đóng gói an toàn trong struct

	_ = balance // Xóa dòng này khi bạn sử dụng balance
	_ = pin     // Xóa dòng này khi bạn sử dụng pin
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online