プログラミングをチームで効率よく開発するためには、担当領域を決めたり、似たようなコードが作らないようにするなどの工夫が必要となります。
プログラムを役割や機能に応じて分割することをモジュール分割と言ったりしますが、その際に必要となる概念が構造体です。
適切に構造体を扱うことで、無駄がなくて読みやすく、メンテナンスのしやすいプログラムにすることができます。
プログラミング初心者にとっては、理解が難しい分野ではありますが、チームで開発するプログラマとなるためには必須の知識となります。
本記事では、Goの構造体とインタフェースについてまとめます。
プログラミング初心者の方の学習や、忘れてしまった方の復習として、参考にしていただければ幸いです。
記載しているプログラムは、Go1.15.2を使って動作確認をしています。
Goの構造体とインタフェース
Goの構造体とインタフェースについて、以下の内容を採り上げます。
- 構造体の定義方法
- コンストラクタ
- 構造体の作成
- メソッド
- フィールド変数
- 構造体の埋め込み
- インタフェース
構造体の定義方法
構造体とは、動作(メソッド)や状態(プロパティ)を1つの型として表したものです。
構造体を定義するには、type と struct キーワードによって宣言します。
例えばPerson構造体を宣言するには、以下のように記述します。
type Person struct { }
コンストラクタ
コンストラクタは構造体の生成とともに初期化をするための関数です。
コンストラクタの生成に他の言語ような決まった形はありませんが、慣習的に構造体名の先頭にnewを付けます。
type Person struct { } func newPerson() *Person { person := new(Person) return person }
構造体の作成
定義した構造体を利用するためには、実体を作成する必要があります。
構造体定義は実体を作るための雛形やテンプレートのようなものであり、実体を作ることで利用できるようになります。
実体を作るにはコンストラクタを呼出す方法のほか、直接構造体を宣言することでも作成することができます。
// コンストラクタで作成 var person1 *Person = newPerson() // 直接構造体を作成 person2 := Person{}
メソッド
構造体におけるメソッドの役割は、その構造体の動作(振る舞い)を定義するものになります。
例えば、Person(人間)クラスであれば、歩く、食べる、寝るという動作が構造体としてのメソッドになります。
構造体のメソッドを定義するには、レシーバ引数に構造体を指定します。
type Person struct { } func (p Person) Walk() { fmt.Println("歩きます") }
上記の構造体定義を作成して実行すると、以下のようになります。
var person1 *Person = newPerson() person1.Walk() // -> 歩きます
フィールド変数
構造体が保持する変数をフィールド変数と呼びます。
構造体のフィールド変数に値を設定するには、コンストラクタで設定する方法と、作成した構造体に直接設定する方法があります。
コンストラクタで設定
コンストラクタの引数で設定したい値を受取り、フィールド変数に代入します。
type Person struct { age int } func newPerson(age int) *Person { person := new(Person) person.age = age return person } func (p Person) SayAge() { fmt.Println(fmt.Sprintf("年齢は%d歳です", p.age)) }
上記の構造体定義を作成する際に、引数にフィールド変数に設定する値を指定します。
作成時は、コンストラクタに指定する方法と、直接作成時に指定する方法のどちらでも可能です。
var person1 *Person = newPerson(30) person1.SayAge() // -> 年齢は30歳です person2 := Person{age: 30} person2.SayAge() // -> 年齢は30歳です
作成した構造体に直接設定
作成した構造体のフィールド変数に対して、直接設定することも可能です。
var person1 *Person = newPerson(30) person1.SayAge() // -> 年齢は30歳です person1.age = 20 person1.SayAge() // -> 年齢は20歳です
構造体の埋め込み
Goにはクラスの概念がないため継承もないのですが、構造体の埋め込みを利用することで似たようなことができます。
type Child struct { Person } func (c Child) Walk() { c.Person.Walk() }
上記の構造体を作成してWalkメソッドを呼出すと以下のようになります。
child := Child{} child.Walk() // -> 歩きます
同名のメソッド名でもOKなので、オーバーライドすることもできます。
しかしこの方法では、型が固定化されてしまっているので、ポリモーフィズムのような使い方はできません。
そこでインタフェースを使います。
インタフェース
Goにおけるインタフェースは、型の1つでメソッド定義のみ行います。
インタフェースを定義するには、type と interface を使って以下のように行います。
type Mouth interface { Eat() }
上記では、Mouthというインタフェース名に、Eatというメソッド定義のみを行っています。
これをPerson構造体とChild構造体のそれぞれで処理の中身を実装します。
func (p Person) Eat() { fmt.Println("食べます") } func (c Child) Eat() { fmt.Print("たくさん") c.Person.Eat() }
上記のプログラムをインタフェースを使って呼出します。
mouthes := []Mouth{Person{}, Child{}} for _, mouth := range mouthes { mouth.Eat() // -> 食べます -> たくさん食べます }
単なる構造体の埋め込みと違うのは、インタフェースの型でそれぞれの構造体を扱っている点です。
上記のプログラムでは、Person構造体とChild構造体を作成して、Mouthインタフェースの配列に追加しています。
ループの中でMouth型の変数に対してEatメソッドを呼出すと、それぞれ異なる処理を行います。
インタフェースを利用することで、このように同じMouth型のメソッドを呼出しても、実際はそれぞれ異なる構造体の処理を行う事ができるようになります。
良いプログラマになるには構造体やインタフェース設計が重要
プログラマは単にプログラミングするだけではなく、設計ができることも重要です。
チームで効率よく開発を進めるには、どんな構造体設計、インタフェース設計とするのかは重要な要素です。
Goはオブジェクト指向言語ではないと言われますが、適切に構造体分割やインタフェース分割が必要な点は他の言語と同じです。
良い設計ができるプログラマを目指して行きましょう。
今回はGoの構造体とインタフェースについてまとめました。
以上、参考になれば幸いです。
コメント