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.
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 structBankAccounttrong packagemain, 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 khaiAccountType(đượ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
NewBankAccountnhậ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ộtBankAccountmới. Đồng thời thêm phương thứcGetPublicInfo() stringtrả 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ộtBankAccountbằ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 (balancevà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: SavingsLư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
}
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
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online