Đóng gói ở cấp package
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 48 trên 107.
Mặc dù bài học trước đã đề cập đến khả năng hiển thị ở cấp trường, tính đóng gói trong Go thực chất hoạt động ở cấp gói. Mọi thứ trong cùng một gói, bất kể nằm trong tệp nào, đều có thể truy cập các trường và hàm chưa được xuất.
Hãy xem xét một package có nhiều tệp:
// account/account.go
package account
type Account struct {
balance int // không công khai
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // Hoạt động vì cùng package
}
Cả hai tệp đều thuộc package account, nên helper.go có thể truy cập trực tiếp vào trường chưa xuất khẩu balance. Điều này là có chủ đích. Cách này cho phép bạn chia logic của package thành nhiều tệp mà vẫn giữ quyền truy cập nội bộ.
Tuy nhiên, code bên ngoài package không thể truy cập các thành viên chưa được export:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // hoạt động: phương thức (method) được xuất
// acc.balance = 50 // lỗi: trường không được xuất
// account.ResetAccount // hoạt động nếu ResetAccount được xuất
}
Ranh giới ở cấp package này là cơ chế đóng gói chính của Go. Bạn thiết kế các package thành những đơn vị thống nhất, trong đó code nội bộ phối hợp tự do, còn code bên ngoài chỉ tương tác với API được xuất ra. Hãy xem mỗi package như một module độc lập, có giao diện công khai rõ ràng.
Thử thách
DễHãy xây dựng một hệ thống quản lý hàng tồn kho để minh họa cách nhiều tệp trong cùng một package có thể phối hợp với nhau bằng cách truy cập trực tiếp vào các trường không được xuất, đồng thời giữ kín dữ liệu đó với code bên ngoài.
Bạn sẽ tạo ba tệp phối hợp với nhau trong package main:
product.go: Định nghĩa một cấu trúcProductcó trường được xuấtNamevà trường không được xuấtquantity(int). Thêm một phương thức được xuấtInfo() stringtrả về định dạng:[Name]: [quantity] in stockinventory.go: Tạo các hàm trợ giúp thao tác với sản phẩm bằng cách truy cập trực tiếp vào trường không được xuấtquantity(có thể làm vậy vì chúng nằm trong cùng một package). Hãy triển khai:Restock(p *Product, amount int): cộng amount vào quantity của sản phẩmSell(p *Product, amount int) bool: trừ amount khỏi quantity nếu còn đủ hàng, trả vềtruenếu thành công hoặcfalsenếu không đủ hàngNewProduct(name string, initialQty int) *Product: hàm khởi tạo tạo một sản phẩm với tên và số lượng ban đầu đã cho
main.go: Đọc thông tin sản phẩm từ đầu vào, tạo sản phẩm bằng hàm khởi tạo, thực hiện các thao tác với hàng tồn kho và hiển thị kết quả. Sau mỗi thao tác, in thông tin sản phẩm để thể hiện trạng thái đã cập nhật.
Đầu vào sẽ có các dòng sau:
- Dòng 1: Tên sản phẩm
- Dòng 2: Số lượng ban đầu (số nguyên)
- Dòng 3: Số lượng nhập thêm (số nguyên)
- Dòng 4: Số lượng bán (số nguyên)
Sau khi tạo sản phẩm, hãy in thông tin ban đầu của sản phẩm. Sau đó nhập thêm hàng và in lại thông tin. Cuối cùng, thử bán hàng và in thông tin đã cập nhật nếu bán thành công, hoặc Sale failed: insufficient stock nếu không đủ số lượng.
Ví dụ, với đầu vào Laptop, 10, 5 và 8, đầu ra sẽ là:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockVới đầu vào Phone, 3, 2 và 10, đầu ra sẽ là:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockĐiểm mấu chốt ở đây là inventory.go có thể đọc và sửa đổi trực tiếp trường không được xuất quantity vì nó nằm trong cùng package với product.go. Đây là cách đóng gói ở cấp package: các thành phần bên trong có thể tự do phối hợp với nhau, trong khi dữ liệu vẫn được bảo vệ khỏi các package bên ngoài.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc tên sản phẩm
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// Đọc số lượng ban đầu
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// Đọc số lượng nhập thêm
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// Đọc số lượng cần bán
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: Tạo sản phẩm mới bằng hàm khởi tạo NewProduct
// TODO: In ra thông tin ban đầu của sản phẩm
// TODO: Nhập thêm hàng và in ra thông tin sản phẩm
// TODO: Thử bán sản phẩm và in ra kết quả
// Nếu bán thành công, in ra thông tin đã cập nhật
// Nếu bán không thành công, in ra "Sale failed: insufficient stock"
fmt.Println() // Thay bằng đầu ra thực tế
}
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