Menu
CoddyTech

Ẩn thông tin trong Go

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 50 trên 107.

Ẩn thông tin không chỉ đơn thuần là làm cho các trường không được xuất. Đó là việc thiết kế các kiểu dữ liệu sao cho code bên ngoài chỉ biết những gì cần biết. Không hơn.

Mục tiêu là thể hiện hành vi thông qua các phương thức, đồng thời giữ kín hoàn toàn cấu trúc bên trong. Hãy xem cách làm này:

type Counter struct {
    value    int
    maxLimit int
}

func NewCounter(limit int) *Counter {
    return &Counter{maxLimit: limit}
}

func (c *Counter) Increment() bool {
    if c.value >= c.maxLimit {
        return false
    }
    c.value++
    return true
}

func (c *Counter) Value() int {
    return c.value
}

Code bên ngoài không biết Counter dùng int bên trong. Nó có thể là float64, một lát cắt hoặc một kiểu hoàn toàn khác. Bạn có thể thay đổi cách triển khai mà không ảnh hưởng đến code sử dụng Counter, miễn là các phương thức vẫn hoạt động như cũ.

Nguyên tắc này cũng áp dụng cho các hàm hỗ trợ. Giữ logic nội bộ không được xuất:

// Đã xuất, thuộc API công khai
func (c *Counter) Reset() {
    c.value = 0
}

// Chưa xuất, dùng để hỗ trợ nội bộ
func (c *Counter) isAtLimit() bool {
    return c.value >= c.maxLimit
}

Bằng cách ẩn isAtLimit(), bạn có thể tự do thay đổi hoặc xóa nó sau này. Tính đóng gói thông tin tạo ra ranh giới rõ ràng giữa những gì package của bạn cam kết với người dùng bên ngoài và cách package hoạt động bên trong.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống ví an toàn để minh họa tính ẩn thông tin. Bạn sẽ tạo một ví kỹ thuật số, trong đó việc theo dõi số dư nội bộ và lịch sử giao dịch hoàn toàn được ẩn khỏi code bên ngoài: chỉ có hành vi của ví được thể hiện thông qua các phương thức.

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

  • wallet.go: Tạo một cấu trúc Wallet ẩn toàn bộ trạng thái nội bộ. Ví cần theo dõi số dư hiện tại và hạn mức chi tiêu ở bên trong, nhưng code bên ngoài không được biết chúng được lưu trữ hay biểu diễn như thế nào. Cung cấp các hành vi thông qua những phương thức sau:
    • NewWallet(limit float64) *Wallet - hàm khởi tạo tạo một ví với hạn mức chi tiêu đã cho và số dư bằng 0
    • Deposit(amount float64) bool - nạp tiền vào ví, trả về true nếu thành công (số tiền phải lớn hơn 0)
    • Spend(amount float64) bool - trừ tiền nếu số dư đủ VÀ số tiền không vượt quá hạn mức chi tiêu, trả về true nếu thành công
    • Balance() float64 - trả về số dư hiện tại
    • Status() string - trả về thông báo trạng thái theo định dạng: Balance: $[balance] (Limit: $[limit])
    Giữ mọi logic hỗ trợ (chẳng hạn như kiểm tra giao dịch có hợp lệ hay không) trong các hàm hoặc phương thức không được xuất ra. Đây là những chi tiết triển khai nội bộ có thể thay đổi sau này.
  • main.go: Đọc cấu hình ví và các giao dịch từ đầu vào, tạo ví, thực hiện các thao tác và hiển thị kết quả sau mỗi thao tác.

Đầu vào sẽ gồm những thông tin sau:

  • Dòng 1: Hạn mức chi tiêu
  • Dòng 2: Số tiền nạp
  • Dòng 3: Số tiền chi tiêu lần đầu
  • Dòng 4: Số tiền chi tiêu lần thứ hai

Sau khi tạo ví, hãy in trạng thái ban đầu. Tiếp đó, thực hiện từng thao tác và in trạng thái đã cập nhật nếu thành công, hoặc Transaction failed nếu thao tác bị từ chối. Định dạng tất cả số tiền theo đô la với hai chữ số thập phân.

Ví dụ, với các giá trị 50, 100, 30 và 80, kết quả đầu ra sẽ là:

Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failed

Giao dịch cuối cùng thất bại vì $80 vượt quá hạn mức chi tiêu $50, dù số dư vẫn đủ. Ví tự thực thi các quy tắc mà không tiết lộ cách đưa ra quyết định. Code bên ngoài chỉ cần gọi các phương thức và nhận kết quả, mà không biết các chi tiết triển khai nội bộ.

Tự làm thử

package main

import (
	"fmt"
)

func main() {
	// Đọc các giá trị đầu vào
	var limit float64
	var depositAmount float64
	var spend1 float64
	var spend2 float64

	fmt.Scanln(&limit)
	fmt.Scanln(&depositAmount)
	fmt.Scanln(&spend1)
	fmt.Scanln(&spend2)

	// TODO: Tạo ví mới với hạn mức chi tiêu đã cho

	// TODO: In ra trạng thái ban đầu

	// TODO: Nạp tiền và in ra kết quả (trạng thái hoặc "Transaction failed")

	// TODO: Chi tiêu lần đầu và in ra kết quả

	// TODO: Chi tiêu lần thứ hai và in ra kết quả
}
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