Menu
Coddy logo textTech

Go における情報隠蔽

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 50/107。

情報隠蔽は、単にフィールドを非公開(unexported)にするだけではありません。それは、外部のコードが必要な情報のみを知り、それ以上のことは知らなくて済むように型を設計することを意味します。

目的は、メソッドを通じて振る舞いを公開する一方で、内部構造を完全に隠蔽したままにすることです。次のアプローチを検討してください:

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

チャレンジ

簡単

情報の隠蔽(情報隠蔽)を実証する、安全なウォレットシステムを構築しましょう。内部の残高管理や取引履歴が外部のコードから完全に隠され、メソッドを通じて振る舞いのみが公開されるデジタルウォレットを作成します。

コードは2つのファイルに分けて構成します:

  • 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行目: 支出制限(Spending limit)
  • 2行目: 入金額(Deposit amount)
  • 3行目: 1回目の支出額(First spend amount)
  • 4行目: 2回目の支出額(Second spend amount)

ウォレットを作成した後、その初期ステータスを出力してください。その後、各操作を実行し、更新されたステータス(成功した場合)または Transaction failed(操作が拒否された場合)のいずれかを出力します。すべてのドル金額は小数点以下2桁でフォーマットしてください。

例えば、501003080 が与えられた場合、出力は以下のようになります:

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

最後の取引は、十分な残高があるにもかかわらず、$80 が $50 の支出制限を超えているため失敗します。ウォレットは、どのように判断を下しているかを外部に公開することなく、内部でルールを強制します。外部のコードは単にメソッドを呼び出して結果を受け取るだけで、内部の実装詳細を知ることはありません。

チートシート

情報隠蔽とは、外部のコードが必要なことだけを知るように型を設計することを意味します。内部構造を完全に隠したまま、メソッドを通じて振る舞いを公開します。

実装が隠蔽された型の例:

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
}

外部のコードは内部表現(intfloat64、あるいはその他の何であるか)を知りません。それを使用するコードに影響を与えることなく、実装を変更することができます。

ヘルパー関数は非公開(unexported)のままにします:

// Exported - 公開APIの一部
func (c *Counter) Reset() {
    c.value = 0
}

// unexported - 内部ヘルパー
func (c *Counter) isAtLimit() bool {
    return c.value >= c.maxLimit
}

情報隠蔽は、パッケージが外部に対して約束することと、内部でどのように動作するかの間に明確な境界線を作ります。

自分で試してみよう

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: 1回目の支出を実行し、結果を表示する

	// TODO: 2回目の支出を実行し、結果を表示する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン