Menu
CoddyTech

Go 中的信息隐藏

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 50 课,共 107 课。

信息隐藏不仅仅是将字段设为非导出。它的核心是设计你的类型,让外部代码只了解它需要了解的内容,不多不少。

目标是通过方法展现行为,同时完全隐藏内部结构。考虑以下做法:

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
}

外部代码并不知道 Counter 在内部使用的是 int。它也可以是 float64、切片,或完全不同的东西。只要方法的行为保持不变,实现就可以更改,而不会影响任何使用 Counter 的代码。

这个原则也适用于辅助函数。不要导出内部逻辑:

// 已导出,属于公开 API
func (c *Counter) Reset() {
    c.value = 0
}

// 未导出,仅供内部使用的辅助方法
func (c *Counter) isAtLimit() bool {
    return c.value >= c.maxLimit
}

通过隐藏 isAtLimit(),你可以在之后自由修改或删除它。信息隐藏在你的包向外部用户承诺的内容与其内部工作方式之间划定了清晰的界限。

challenge icon

挑战

简单

让我们构建一个安全的钱包系统,展示信息隐藏。你将创建一个数字钱包,其内部余额跟踪和交易历史记录完全对外部代码隐藏:只通过方法暴露行为。

你将把代码分成两个文件:

  • wallet.go:创建一个隐藏所有内部状态的 Wallet 结构体。钱包应在内部跟踪当前余额和消费限额,但外部代码不应知道这些数据的存储或表示方式。通过以下方法暴露行为:
    • NewWallet(limit float64) *Wallet:构造函数,用给定的消费限额和零余额创建钱包
    • Deposit(amount float64) bool:向钱包存入资金,如果成功则返回 true(金额必须为正数)
    • Spend(amount float64) bool:如果余额充足且金额不超过消费限额,则扣除资金;如果成功则返回 true
    • Balance() float64:返回当前余额
    • Status() string:返回符合以下格式的状态消息:Balance: $[balance] (Limit: $[limit])
    将辅助逻辑(例如检查交易是否有效)保留为未导出的函数或方法。这些是内部实现细节,之后可能会发生变化。
  • main.go:从输入中读取钱包配置和交易,创建钱包,执行操作,并在每次操作后显示结果。

将提供以下输入:

  • 第 1 行:消费限额
  • 第 2 行:存款金额
  • 第 3 行:第一次消费金额
  • 第 4 行:第二次消费金额

创建钱包后,打印其初始状态。然后执行每项操作,并打印更新后的状态(如果操作成功)或 Transaction failed(如果操作被拒绝)。所有金额都使用两位小数格式。

例如,给定 50、100、30 和 80,你的输出应为:

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

最后一笔交易失败,因为 $80 超过了 $50 的消费限额,即使余额充足也是如此。钱包会在内部执行规则,而不会暴露其作出这些判断的方式。外部代码只需调用方法并接收结果,无需了解内部实现细节。

亲手试试

package main

import (
	"fmt"
)

func main() {
	// 读取输入
	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: 使用给定的消费限额创建一个新钱包

	// TODO: 打印初始状态

	// TODO: 存款并打印结果(状态或 "Transaction failed")

	// TODO: 进行第一次消费并打印结果

	// TODO: 进行第二次消费并打印结果
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器