Menu
Coddy logo textTech

パッケージレベルのカプセル化

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

前のレッスンではフィールド単位の可視性を扱いましたが、Go のカプセル化は実際にはパッケージ単位で機能します。同じパッケージ内にあるものは、どのファイルに含まれているかに関係なく、非エクスポートのフィールドや関数にアクセスできます。

複数のファイルを含むパッケージについて考えてみましょう:

// account/account.go
package account

type Account struct {
    balance int  // 非公開
}

func (a *Account) Deposit(amount int) {
    a.balance += amount
}
// account/helper.go
package account

func ResetAccount(a *Account) {
    a.balance = 0  // 動作する - 同じパッケージ
}

両方のファイルはaccountパッケージに属しているため、helper.goは非エクスポートのbalanceフィールドに直接アクセスできます。これは意図的なものです。これにより、内部アクセスを維持しながら、パッケージのロジックを複数のファイルに分割できます。

ただし、パッケージの外部にあるコードからは、エクスポートされていないメンバーにアクセスできません。

// main.go
package main

import "account"

func main() {
    acc := &account.Account{}
    acc.Deposit(100)        // 動作する - エクスポートされたメソッド
    // acc.balance = 50     // エラー - 未エクスポートのフィールド
    // account.ResetAccount // ResetAccount がエクスポートされていれば動作する
}

このパッケージレベルの境界は、Goにおける主要なカプセル化メカニズムです。内部コードが自由に連携し、外部コードはExported APIを通じてのみやり取りする、まとまりのある単位としてパッケージを設計します。それぞれのパッケージを、明確な公開インターフェースを備えた自己完結型のモジュールと考えてください。

challenge icon

チャレンジ

簡単

外部コードからデータを隠したまま、同じパッケージ内の複数のファイルが非エクスポートフィールドに直接 access して連携する方法を示す、inventory 管理システムを構築しましょう。

main パッケージ内で連携する3つのファイルを作成します。

  • product.go:エクスポートされた Name フィールドと、非エクスポートの quantity フィールド(int)を持つ Product 構造体を定義します。[Name]: [quantity] in stock という format を返す、エクスポートされたメソッド Info() string を追加します。
  • inventory.go:非エクスポートの quantity フィールドに直接 access して product を扱う helper function を作成します(同じパッケージ内にあるため可能です)。次を Implement します。
    • Restock(p *Product, amount int) - product の quantity に amount を adds します
    • Sell(p *Product, amount int) bool - 十分な在庫がある場合は quantity から amount を引き、成功した場合は true、在庫が enough でない場合は false を返します
    • NewProduct(name string, initialQty int) *Product - given name と initial quantity を持つ product を Create する constructor です
  • main.go:入力から product の詳細を読み取り、constructor を使用して product を Create し、inventory 操作を実行して結果を表示します。各操作の後に product の info を表示し、更新された状態を示します。

次の入力が提供されます。

  • 1行目:Product name
  • 2行目:Initial quantity(integer)
  • 3行目:Restock amount(integer)
  • 4行目:Sell amount(integer)

product を Create した後、その initial info を表示します。次に restock を行い、もう一度 info を表示します。最後に sell を試み、成功した場合は更新された info を、quantity が enough でない場合は Sale failed: insufficient stock を表示します。

たとえば、Laptop1058 が given された場合、出力は次のようになります。

Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stock

また、Phone3210 が given された場合、出力は次のようになります。

Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stock

ここでの key insight は、inventory.go が非エクスポートの quantity フィールドを直接 read して modify できることです。これは product.go と同じパッケージにあるためです。これはパッケージレベルのカプセル化の実例です。内部の連携は自由に行われる一方で、データは外部パッケージから protected な状態に保たれます。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 商品名を読み取る
	name, _ := reader.ReadString('\n')
	name = strings.TrimSpace(name)

	// 初期数量を読み取る
	initialQtyStr, _ := reader.ReadString('\n')
	initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))

	// 補充量を読み取る
	restockStr, _ := reader.ReadString('\n')
	restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))

	// 販売量を読み取る
	sellStr, _ := reader.ReadString('\n')
	sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))

	// TODO: NewProductコンストラクタを使用して新しい商品を作成する

	// TODO: 初期の商品情報を出力する

	// TODO: 商品を補充して情報を出力する

	// TODO: 販売を試みて結果を出力する
	// 販売が成功した場合、更新された情報を出力する
	// If sale fails, print "Sale failed: insufficient stock"

	fmt.Println() // 実際の出力に置き換える
}
quiz icon腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ