パッケージレベルのカプセル化
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) // works - exported method
// acc.balance = 50 // error - unexported field
// account.ResetAccount // works if ResetAccount were exported
}
このパッケージレベルの境界は、Goの主要なカプセル化メカニズムです。パッケージを、内部コードが自由に連携し、外部コードはエクスポートされたAPIを通じてのみ相互作用する、凝集度の高いユニットとして設計します。各パッケージを、明確な公開インターフェースを持つ自己完結型のモジュールと考えてください。
チャレンジ
簡単同じパッケージ内の複数のファイルが、非公開フィールドに直接アクセスすることで連携しつつ、そのデータを外部コードから隠蔽する方法を示す在庫管理システムを構築しましょう。
main パッケージ内で連携して動作する3つのファイルを作成します:
product.go: 公開フィールドNameと非公開フィールドquantity(int) を持つProduct構造体を定義します。[Name]: [quantity] in stockという形式を返す公開メソッドInfo() stringを追加してください。inventory.go: 非公開のquantityフィールドに直接アクセスして製品を操作するヘルパー関数を作成します(同じパッケージ内にあるため可能です)。以下を実装してください:Restock(p *Product, amount int)- 製品の数量に指定された量を追加します。Sell(p *Product, amount int) bool- 十分な在庫がある場合に数量から指定された量を差し引き、成功した場合はtrue、在庫不足の場合はfalseを返します。NewProduct(name string, initialQty int) *Product- 指定された名前と初期数量で製品を作成するコンストラクタです。
main.go: 入力から製品の詳細を読み取り、コンストラクタを使用して製品を作成し、在庫操作を実行して結果を表示します。各操作の後、製品情報を出力して更新された状態を表示してください。
以下の入力が提供されます:
- 1行目: 製品名
- 2行目: 初期数量 (整数)
- 3行目: 補充量 (整数)
- 4行目: 販売量 (整数)
製品を作成した後、その初期情報を出力します。次に補充を行い、再度情報を出力します。最後に販売を試み、更新された情報(成功した場合)または "Sale failed: insufficient stock"(数量が不足している場合)のいずれかを出力します。
例えば、Laptop、10、5、8 が与えられた場合、出力は以下のようになります:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockまた、Phone、3、2、10 が与えられた場合、出力は以下のようになります:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stockここでの重要なポイントは、inventory.go が product.go と同じパッケージ内にあるため、非公開の quantity フィールドを直接読み書きできることです。これはパッケージレベルのカプセル化の実践です。内部の連携は自由に行われる一方で、データは外部パッケージから保護されたままになります。
チートシート
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 // 動作する - 同じパッケージ
}
外部パッケージの制限
パッケージ外のコードは、エクスポートされていないメンバにアクセスできません:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // 動作する - エクスポートされたメソッド
// acc.balance = 50 // エラー - エクスポートされていないフィールド
}
この設計により、内部アクセスを維持しながらパッケージのロジックを複数のファイルに分割することができ、明確なパブリックインターフェースを持つまとまりのあるユニットを作成できます。
自分で試してみよう
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: 販売を試行し、結果を表示する
// 販売が成功した場合は、更新された情報を表示する
// 販売が失敗した場合は、"Sale failed: insufficient stock" と表示する
fmt.Println() // 実際の出力に置き換える
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー