公開フィールドと非公開フィールド
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 47/107。
Goでは、構造体レベルでのカプセル化は、フィールド名の大文字・小文字によって制御されます。この単純なルールによって、構造体が定義されているパッケージの外からフィールドにアクセスできるかどうかが決まります。
大文字で始まるフィールドはエクスポートされ、どのパッケージからでもアクセス可能です。小文字で始まるフィールドはエクスポートされず、同じパッケージ内からのみアクセス可能です:
// パッケージ "user" 内
type User struct {
Name string // 公開 - どこからでもアクセス可能
Email string // 公開 - どこからでもアクセス可能
password string // 非公開 - "user" パッケージ内でのみアクセス可能
age int // 非公開 - "user" パッケージ内でのみアクセス可能
}
別のパッケージがこの構造体をインポートして使用する場合、エクスポートされたフィールドとしかやり取りできません。
// パッケージ "main" 内
import "user"
func main() {
u := user.User{
Name: "Alice", // 動作します
Email: "a@mail.com", // 動作します
// password: "secret", // コンパイルエラー: 未知のフィールドです
}
fmt.Println(u.Name) // 動作します
// fmt.Println(u.password) // コンパイルエラー: エクスポートされていないフィールドは参照できません
}
このメカニズムは、機密データや内部の実装の詳細を保護します。passwordフィールドはパッケージの外部から直接読み取ったり変更したりすることはできず、他のコードは提供されたメソッドを使用することを強制されます。これは、オブジェクト指向プログラミングにおけるカプセル化の核心的な原則である「データ隠蔽」を実現するためのGoのアプローチです。
チャレンジ
簡単エクスポートされたフィールドとエクスポートされていないフィールドを通じてカプセル化を実証する銀行口座システムを構築しましょう。機密性の高い財務データを保護しながら、安全な公開情報を公開するパッケージを作成します。
コードを2つのファイルに分けて構成します:
account.go: 適切なデータ保護を備えた実際の銀行口座をモデル化したBankAccount構造体をmainパッケージに作成します:HolderName(エクスポートあり) - 口座名義人の名前。公開しても安全です。AccountType(エクスポートあり) - 口座の種類(例: "Savings", "Checking")balance(エクスポートなし) - 実際の残高。保護される必要があります。pin(エクスポートなし) - 口座のPIN。非常に機密性の高いデータです。
BankAccountへのポインタを返すコンストラクタ関数NewBankAccountを追加します。また、Account: [HolderName] ([AccountType])という形式を返すメソッドGetPublicInfo() stringを追加します。main.go: 入力から口座の詳細を読み取り、コンストラクタを使用してBankAccountを作成し、カプセル化を実証します。メソッドを使用して公開情報を出力し、次に各エクスポートされたフィールドに直接アクセスして別々の行に出力します。エクスポートされていないフィールド(balanceとpin)は保護されており、コンストラクタを通じてのみ設定できます。
以下の入力が提供されます:
- 1行目: 口座名義人の名前
- 2行目: 口座の種類
- 3行目: 初期残高(浮動小数点数として)
- 4行目: PINコード
例えば、Alice Johnson, Savings, 5000.50, 1234 が与えられた場合、出力は以下のようになります:
Account: Alice Johnson (Savings)
Holder: Alice Johnson
Type: Savings残高とPINが出力に表示されないことに注目してください。これらは構造体の中に安全に隠されており、パッケージ内からのみアクセス可能です。これがカプセル化の仕組みです。機密データは保護され、公開情報はアクセス可能なまま維持されます。
チートシート
Goでは、カプセル化は構造体のフィールド名の先頭文字が大文字か小文字かによって制御されます:
- 大文字で始まるフィールドはエクスポートされます(どのパッケージからでもアクセス可能です)
- 小文字で始まるフィールドはエクスポートされません(同じパッケージ内からのみアクセス可能です)
type User struct {
Name string // エクスポートされる - どこからでもアクセス可能
Email string // エクスポートされる - どこからでもアクセス可能
password string // エクスポートされない - 同じパッケージ内からのみアクセス可能
age int // エクスポートされない - 同じパッケージ内からのみアクセス可能
}
別のパッケージの構造体を使用する場合、エクスポートされたフィールドのみがアクセス可能です:
import "user"
func main() {
u := user.User{
Name: "Alice", // 動作する
Email: "a@mail.com", // 動作する
// password: "secret", // コンパイルエラー: 未知のフィールド
}
fmt.Println(u.Name) // 動作する
// fmt.Println(u.password) // コンパイルエラー: エクスポートされていないフィールドは参照できません
}
この仕組みは、機密データや内部の実装詳細を保護し、外部のコードがアクセスするために提供されたメソッドを使用するように強制します。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 口座名義人を読み込む
holderName, _ := reader.ReadString('\n')
holderName = strings.TrimSpace(holderName)
// 口座種別を読み込む
accountType, _ := reader.ReadString('\n')
accountType = strings.TrimSpace(accountType)
// 初期残高を読み込む
balanceStr, _ := reader.ReadString('\n')
balanceStr = strings.TrimSpace(balanceStr)
balance, _ := strconv.ParseFloat(balanceStr, 64)
// PINを読み込む
pinStr, _ := reader.ReadString('\n')
pin := strings.TrimSpace(pinStr)
// TODO: コンストラクタを使用して新しい BankAccount を作成する
// account := NewBankAccount(...)
// TODO: GetPublicInfo() メソッドを使用して公開情報を出力する
// TODO: エクスポートされたフィールドに直接アクセスして名義人名を出力する
// fmt.Println("Holder:", ...)
// TODO: エクスポートされたフィールドに直接アクセスして口座種別を出力する
// fmt.Println("Type:", ...)
// Note: balance と pin はエクスポートされていません。ここでは直接アクセスできません
// これらは構造体内に安全にカプセル化されています
_ = balance // balance を使用する際は、この行を削除してください
_ = pin // pin を使用する際は、この行を削除してください
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー