パッケージレベルのカプセル化
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を通じてのみやり取りする、まとまりのある単位としてパッケージを設計します。それぞれのパッケージを、明確な公開インターフェースを備えた自己完結型のモジュールと考えてください。
チャレンジ
簡単外部コードからデータを隠したまま、同じパッケージ内の複数のファイルが非エクスポートフィールドに直接 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 を表示します。
たとえば、Laptop、10、5、8 が given された場合、出力は次のようになります。
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stockまた、Phone、3、2、10 が 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() // 実際の出力に置き換える
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ