今回は、EncapsulationとEmbeddingを学びましょう。
ここでは、EncapsulationはStructのカプセル化、Embeddingは、Structの拡張を指しています。
学習記事まとめ
Structの作成
Structについて少し復習しておきましょう。
Structは、Go言語の構造体の一つで、様々なtypeのデータをまとめて管理することができます。

私の感覚だとクラスみたいなものです。
サンプルコードを下記に記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" type Date struct { Year int Month int Day int } func main() { date := Date{Year: 2019, Month: 10, Day: 29} fmt.Println(date) } |
DateのStructを実装してみました。
実行してみます。
1 2 3 |
$ go run main.go {2019 10 29} |
Setterメソッド
先ほどの例では、Date Structに設定したパラメータ(Year, Month, Day)に対して、正しい値を入力しました。
しかし、実際には存在しえない値(無効な値)を入力したらどうなるのでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" type Date struct { Year int Month int Day int } func main() { date := Date{Year: 9999, Month: 99, Day: 99} fmt.Println(date) } |
1 2 3 |
$ go run main.go {9999 99 99} |
Date Structに無効な値を入力して実行しましたが、普通に処理されてしまいましたね。しかし、これでは困るわけです。
アプリ開発の観点では、Structに対してユーザーが自由に値をセットすることは望ましくありません。
Structに値を設定する時は、Setterメソッドを定義することが定石です。
1 2 3 4 5 6 |
// Setterの定義(Setter名は任意だが、Goの慣習的にSetXと定義する) def SetX(){} def SetYear(){} def setMonth(){} def setDay(){} |
サンプルコードを以下に記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package main import "fmt" type Date struct { Year int Month int Day int } func (d Date) SetYear(year int) { d.Year = year } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} date.SetYear(2019) fmt.Println(date.Year) } |
SetYearメソッドを実装しました。これを実行したらどうなるでしょうか?
1 2 3 |
$ go run main.go 0 |
出力結果が、「0」になりましたね。Go言語はデフォルトでは値渡しの言語であるため、引数に渡された値(2019)は、オリジナルからコピーしたものが渡されます。
これを解決するには、メソッドのパラメータをPointer Receiverにする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package main import "fmt" type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) { d.Year = year } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} date.SetYear(2019) fmt.Println(date.Year) } |
変更点は1箇所だけで、(d Date)に「*」を付与しました。
実行してみます。
1 2 3 |
$ go run main.go 2019 |
値をセットできましたね。
同じようにSetMonth, SetDayを作ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package main import "fmt" type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) { d.Year = year } func (d *Date) SetMonth(month int) { d.Month = month } func (d *Date) SetDay(day int) { d.Day = day } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} date.SetYear(2019) date.SetMonth(10) date.SetDay(29) fmt.Println(date.Year) fmt.Println(date.Month) fmt.Println(date.Day) } |
1 2 3 4 5 |
$ go run main.go 2019 10 29 |
簡単ですね。しかし、これではまだ無効なデータを入力できてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package main import "fmt" type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) { d.Year = year } func (d *Date) SetMonth(month int) { d.Month = month } func (d *Date) SetDay(day int) { d.Day = day } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} date.SetYear(9999) date.SetMonth(99) date.SetDay(99) fmt.Println(date.Year) fmt.Println(date.Month) fmt.Println(date.Day) } |
1 2 3 4 5 |
$ go run main.go 9999 99 99 |
バリデーションチェック
不正の値を検出するためにSetterメソッドにバリデーションチェックを追加しましょう。
バリデーションチェックとは、インプットされた値に対してその値が有効な入力値であるか検査することです。

アプリ開発において、バリデーションチェックはよく実装します。例えば、フォームの入力文字数チェックなどもバリデーションチェックですね。
では、最初にSetYearに対してバリデーションチェックを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package main import ( "errors" "fmt" "log" ) type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } d.Year = year return nil } func (d *Date) SetMonth(month int) { d.Month = month } func (d *Date) SetDay(day int) { d.Day = day } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} err := date.SetYear(9999) if err != nil { log.Fatal(err) } fmt.Println(date.Year) } |
Yearのバリデーション範囲は、1 ~ 2100の範囲にしました。この範囲外の場合は、errorを返すように実装しています。
1 2 3 4 |
$ go run main.go 2019/10/21 06:31:22 Invalid year exit status 1 |
同様にSetMonth, SetDayにもバリデーションを追加してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package main import ( "errors" "fmt" "log" ) type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } d.Year = year return nil } func (d *Date) SetMonth(month int) error { if month < 1 || month > 12 { return errors.New("Invalid month") } d.Month = month return nil } func (d *Date) SetDay(day int) error { if day < 1 || day > 31 { return errors.New("Invalid Day") } d.Day = day return nil } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} err := date.SetYear(2019) err = date.SetMonth(10) err = date.SetDay(32) if err != nil { log.Fatal(err) } fmt.Println(date.Year) } |
Dayは、月ごとに最大日数が違いますが、ここでは簡単のために1~31までの数値をバリデーションチェックしています。
しかし、これらのSetterメソッドはまだ”甘い“です。
なぜなら、値を直接書き換えることが可能だからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package main import ( "errors" "fmt" ) type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } d.Year = year return nil } func (d *Date) SetMonth(month int) error { if month < 1 || month > 12 { return errors.New("Invalid month") } d.Month = month return nil } func (d *Date) SetDay(day int) error { if day < 1 || day > 31 { return errors.New("Invalid Day") } d.Day = day return nil } func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := Date{} date.Year = 2019 date.Month = 10 date.Day = 29 fmt.Println(date) } |
1 2 3 |
$ go run main.go {2019 10 29} |
入力情報は、全てSetterメソッドを経由させる必要があります。

どうすれば、直接値を書き換えずに済むか考えてみてください。
パッケージの分割
これまでは、mainパッケージ内に処理を書いていましたが、Date StructとSetterメソッドを別パッケージに分割しましょう。
そして、Structを外部非公開(unexport)状態にして、Setterメソッドを外部公開(export)状態にします。
このようにすれば、先ほどの問題を解決することができます。
最初に次の構成になるよう、WorkSpaceを作成してください。
1 2 3 4 |
workspace L main.go L calendar L date.go |
date.goには、Date StructとSetterメソッドをコピーします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// calendar/date.go package calendar import ( "errors" ) type Date struct { Year int Month int Day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } d.Year = year return nil } func (d *Date) SetMonth(month int) error { if month < 1 || month > 12 { return errors.New("Invalid month") } d.Month = month return nil } func (d *Date) SetDay(day int) error { if day < 1 || day > 31 { return errors.New("Invalid Day") } d.Day = day return nil } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import ( "fmt" "github.com/hoge/calendar" ) func main() { // date := Date{Year: 9999, Month: 99, Day: 99} date := calendar.Date{} date.Year = 2019 date.Month = 10 date.Day = 29 fmt.Println(date) date = calendar.Date{Year: 0, Month: 0, Day: -2} fmt.Println(date) } |
1 2 3 4 |
$ go run main.go {2019 10 29} {0 0 -2} |
次に、Date Structを外部から直接アクセスできないように変更します。
変更は簡単で、Struct の各フィールドを全て小文字にするだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// calendar/date.go package calendar import ( "errors" ) type Date struct { // change to lowercase year int month int day int } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } // change to lowercase d.year = year return nil } func (d *Date) SetMonth(month int) error { if month < 1 || month > 12 { return errors.New("Invalid month") } // change to lowercase d.month = month return nil } func (d *Date) SetDay(day int) error { if day < 1 || day > 31 { return errors.New("Invalid Day") } // change to lowercase d.day = day return nil } |
Go言語では、外部ファイルにフィールドや変数を定義したとき、先頭の文字が小文字の場合は、外部から読み取ることができなくなります。Javaで例えるとprivateを設定したようなものです。
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 |
$ go run main.go # command-line-arguments ./main.go:12:6: date.Year undefined (type calendar.Date has no field or method Year) ./main.go:13:6: date.Month undefined (type calendar.Date has no field or method Month) ./main.go:14:6: date.Day undefined (type calendar.Date has no field or method Day) ./main.go:18:23: unknown field 'Year' in struct literal of type calendar.Date ./main.go:18:32: unknown field 'Month' in struct literal of type calendar.Date ./main.go:18:42: unknown field 'Day' in struct literal of type calendar.Date |
OKですね。これでDate Structのパラメータを変更したい場合は、Setterメソッドを通じて変更するしかなくなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// main.go package main import ( "fmt" "log" "github.com/hoge/calendar" ) func main() { date := calendar.Date{} err := date.SetYear(2019) if err != nil { log.Fatal(err) } err = date.SetMonth(10) if err != nil { log.Fatal(err) } err = date.SetDay(29) if err != nil { log.Fatal(err) } fmt.Println(date) } |
1 2 3 |
$ go run main.go {2019 10 29} |
このように、プライベート用の変数、関数、メソッドは、同一パッケージ内の公開用関数やメソッドを通じてアクセスします。
Getterメソッド
プライベートに設定した変数は外部ファイルから読み取れないようにできました。
しかし、Date Structのフィールドをプライベートに設定してしまうと外部から値を取得することができなくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// main.go package main import ( "fmt" "log" "github.com/hoge/calendar" ) func main() { date := calendar.Date{} err := date.SetYear(2019) if err != nil { log.Fatal(err) } fmt.Println(date.year) } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:16:18: date.year undefined (cannot refer to unexported field or method year) |
このような場合は、Getterメソッドを定義します。
Setterメソッドが、定義したStructのフィールドに値を設定するメソッドであるなら、GetterメソッドはStructのフィールドから値を取得するメソッドです。
サンプルコードを以下に記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// calendar/date.go package calendar import ( "errors" ) type Date struct { // change to lowercase year int month int day int } // Set Getter Method using a pointer receiver type func (d *Date) Year() int { return d.year } func (d *Date) Month() int { return d.month } func (d *Date) Day() int { return d.day } // Change Value Receiver to Pointer Receiver func (d *Date) SetYear(year int) error { if year < 1 || year > 2100 { return errors.New("Invalid year") } // change to lowercase d.year = year return nil } func (d *Date) SetMonth(month int) error { if month < 1 || month > 12 { return errors.New("Invalid month") } // change to lowercase d.month = month return nil } func (d *Date) SetDay(day int) error { if day < 1 || day > 31 { return errors.New("Invalid Day") } // change to lowercase d.day = day return nil } |
Go言語の慣習的に、Getterメソッド名とStructのフィールド名は一致させた方が良いらしいです。例えば、GetterメソッドのYearとDate Structのyearですね。
もちろんGetterメソッド名の先頭は大文字にしてください。外部参照ができなくなるので。
あとは、GetterメソッドのReceiverをPointer Receiverで宣言してください。
これで、Getterメソッドの追加は完了です。main関数を次のように書き換えて、プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// main.go package main import ( "fmt" "log" "github.com/hoge/calendar" ) func main() { date := calendar.Date{} err := date.SetYear(2019) if err != nil { log.Fatal(err) } err = date.SetMonth(10) if err != nil { log.Fatal(err) } err = date.SetDay(29) if err != nil { log.Fatal(err) } fmt.Println(date.Year()) fmt.Println(date.Month()) fmt.Println(date.Day()) } |
1 2 3 4 5 |
$ go run main.go 2019 10 29 |
Go言語のカプセル化(Encapsulation)について
外部からデータを隠蔽するプログラム手法の一つにカプセル化(Encapsulation)があります。
カプセル化は、Go言語特有のものではなく、プログラミングの一般的な概念であり、不正な値から大切なデータを守るプロテクターとして、とても有効です。
他のプログラミング言語では、クラス単位でデータをカプセル化しますが、Go言語にはクラスが存在しないので、パッケージ単位でカプセル化を行います。
また、他のプログラミング言語では、定義されたデータに対して必ずSetterを設定す べきとする言語もありますが、Go言語に関しては必ずしも必要ではありません。バリデーションチェックなど必要に応じてカプセル化を行えば、OKです。
※外部ファイルへのexportを許可してOK
Embedding
Embeddingとは、「拡張する」ことを指します。Structに宣言したフィールドを拡張するときによく使われますね。
例えば、次のサンプルコードのことを指します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package magazine type Subscriber struct { Name string Rate float64 Active bool Address } type Employee struct { Name string Salary float64 Address } type Address struct { // Fields omiited } |
Address Structのフィールドを他のStructのフィールドに定義することで、フィールドの拡張をしています。クラスの継承に少し似ていますね。
さて、calendarフォルダ配下に、event.goファイルを作成してください。
このファイルには、Event StructにDate StructをEmbeddingしたプログラムを実装します。
1 2 3 4 5 6 7 8 9 |
// calendar/event.go package calendar type Event struct { Title string Date } |
これを一旦、main関数から呼び出してみましょう。恐らくエラーになります。
1 2 3 4 5 6 7 8 9 10 11 |
// main.go package main import "github.com/hoge/calendar" func main() { event := calendar.Event{} event.month = 5 } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:7:7: event.month undefined (type calendar.Event has no field or method month) |
予想通りエラーになりましたね。Date Structのmonthは外部ファイルへ非公開になっている為、エラーになります。
外部公開用のメソッドはStructのフィールドにもなり得る!?
先ほどは、Event StructにDate StructをEmbeddingしたプログラムの例を見せました。
しかし、いざプログラムを実行したところ、Date Structのフィールドがunexport状態だった為、コンパルエラーになってしまいましたね。
この状態を回避するには、外部公開用のメソッドを定義すればOKです。
Go言語の面白いところは、この外部公開用のメソッドをStructのフィールドとして宣言できてしまう点です。
サンプルコードをみてください。
1 2 3 4 |
# 新しいディレクトリとファイルを作成しておきます mkdir mypackage cd mypackage/ touch mypackage.go |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mypackage/mypackage.go package mypackage import "fmt" type MyType struct { // Do Embed EmbeddedType } type EmbeddedType string // 外部公開用メソッド func (e EmbeddedType) ExportedMethod() { fmt.Println("Look at ExportedMethod on EmbeededType") } // 非公開メソッド func (e EmbeddedType) unexportedMethod() { } |
EmbeddedTypeフィールドが今回アクセスしたいフィールドですね。
定義したEmbeddedTypeをMyType Structにフィールドとしてセットします。そして、EmbeddedTypeをメソッドとして定義したExportedMethod関数を用意しました。
これをどのように使うかですが、下記のmain関数を見てください。
1 2 3 4 5 6 7 8 9 10 |
// main.go package main import "github.com/hoge/mypackage" func main() { value := mypackage.MyType{} value.ExportedMethod() } |
1 2 3 |
$ go run main.go Look at ExportedMethod on EmbeededType |
こんな感じで使います。
MyType Structは、EmbeddedTypeフィールドを持っているため、EmbeddedTypeのメソッドも使用可能になるわけですね。

ややこしいですよね?わかります。
非公開にしておいたメソッド(unexportedMethod)については、使用することはできません。
1 2 3 4 5 6 7 8 9 10 11 |
// main.go package main import "github.com/hoge/mypackage" func main() { value := mypackage.MyType{} value.unexportedMethod() } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:7:7: value.unexportedMethod undefined (type mypackage.MyType has no field or method unexportedMethod) |
先ほどエラーになったEvent Structもこれと同じことをすれば良いのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// main.go package main import ( "fmt" "log" "github.com/hoge/calendar" ) func main() { event := calendar.Event{} err := event.SetYear(2019) if err != nil { log.Fatal(err) } err = event.SetMonth(10) if err != nil { log.Fatal(err) } err = event.SetDay(29) if err != nil { log.Fatal(err) } fmt.Println(event.Year()) fmt.Println(event.Month()) fmt.Println(event.Day()) } |
1 2 3 4 |
$ go run main.go 2019 10 29 |
簡単ですね^^
やってみよう
Event StructからDate Structのフィールド値を出力することができました。しかし、Event StructのTitleの値を直接書き換えることができてしまう問題が残っています。
1 2 3 4 5 6 7 8 |
// calendar/event.go package calendar type Event struct { Title string // <- ここ Date } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// main.go package main import ( "fmt" "github.com/hoge/calendar" ) func main() { event := calendar.Event{} event.Title = "My Birthday!!!" fmt.Println(event.Title) } |
1 2 3 |
$ go run main.go My Birthday!!! |
このEventのTitleフィールドをカプセル化してみましょう。
要件を書いておきますね。
・ Title値の書き換えやアクセスはSetter/Getterメソッドから行うこと
・ 最大文字数を30文字以内とするバリデーションチェックを入れること
では、event.goファイルを書き換えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// calendar/event.go package calendar import ( "errors" "unicode/utf8" ) type Event struct { title string Date } func (e *Event) Title() string { return e.title } func (e *Event) SetTitle(title string) error { if utf8.RuneCountInString(title) > 30 { return errors.New("Invalid Title") } e.title = title return nil } |
utf8パッケージのRuneCountInString関数を使えば、文字数チェックは簡単にできます。
main関数から呼び出してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import ( "log" "github.com/hoge/calendar" ) func main() { event := calendar.Event{} err := event.SetTitle("Couldn't set a title over 30 strings!!!") if err != nil { log.Fatal(err) } } |
1 2 3 4 |
$ go run main.go 2019/10/22 22:00:28 Invalid Title exit status 1 |
これで、Event Structからは、Titleフィールド + Date Structのフィールドの書き換えや取得が可能になりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// main.go package main import ( "fmt" "log" "github.com/hoge/calendar" ) func main() { event := calendar.Event{} err := event.SetTitle("My Birthday!!!") if err != nil { log.Fatal(err) } err = event.SetYear(2019) if err != nil { log.Fatal(err) } err = event.SetMonth(10) if err != nil { log.Fatal(err) } err = event.SetDay(29) if err != nil { log.Fatal(err) } fmt.Println(event.Title()) fmt.Println(event.Year()) fmt.Println(event.Month()) fmt.Println(event.Day()) } |
1 2 3 4 5 6 |
go run main.go My Birthday!!! 2019 10 29 |
Embeddingは、奥が深そうですね。
まとめ

最後にこの章で学んだことをまとめておきましょう。
・ Go言語では、クラスがない為、パッケージ単位でカプセル化を行う
・ Structフィールドを外部非公開にすることで、データの保護を行う
・ 外部公開用の関数やメソッドにて、非公開フィールド/メソッド/関数にアクセスする
・ フィールド等に値をセットするときは、バリデーションチェックを入れる
・ 外部非公開フィールドに値をセットするメソッドをSetter Methodと呼ぶ
・ Setter Methodでは、Pointer Receiverを使う
・ Setter Method名はSetXと定義し、Xは値のセット先のフィールド名とする
・ 外部非公開フィールドの値を取得するメソッドをGetter Methodと呼ぶ
・ Getter Method名はXと定義し、Xは値の取得先のフィールド名とする
・ 定義したメソッドは、他のStrcutのフィールドにEmbeddingできる
次回
次回は、Interfaceを学びましょう!
コメントを残す
コメントを投稿するにはログインしてください。