Java
Git
golang
um

JavaプログラマがGo言語を触ってみた(Javaの継承をGo言語で実装してみる)

More than 1 year has passed since last update.

image

ども、keita69sawada です。

JavaプログラマがGo言語を触ってみた(とりあえず触ってみる編)に続きGo言語のお勉強。

Javaのクラス設計と同じようにUMLで設計したところ、Go言語では「継承(extends)」が無いので共通処理(抽象メソッド)をどうやって実現すべきかイメージできなかった。。。

じゃ、「簡単なJavaのクラス設計をGo言語で実装してみよう!」と
ウニウニしたのがこの記事です。

対象読者

  • Go言語のソースファイルの作成単位に悩んでいる人
  • Javaの継承(extends)を使った共通処理(抽象メソッド)をGo言語でどうやって実装するか悩んでいる人
  • Go言語の初心者

前提条件

  • Java言語の知識がある人

はじめに

 Javaの継承(extends)をつかった簡単なサンプルをGo言語に置き換えてみます。
「動物」を継承するクラス「イヌ」と「ネコ」に「鳴く」メソッドを実装してみます。

1. クラス図

 厳格なUMLではないですが、こんな感じでしょうか。

image

3. Go言語で同じものを実現しようとすると・・・

以下、悩みました(;´Д`)

  1. Go言語のソースファイルはどう分割すればいいの?(クラスの概念ないし…)
    • GitHubで競合しないようにソースファイルを細かく分けたい。
    • 動物の種類(サルとかキジとか)が増えたら、ソースファイルを増やす設計にしたい。
  2. Go言語では継承(extends)の概念がないので共通処理(抽象メソッド)をどうやって実現すればいいの?

これらの悩みを自分なりに考えてみました。

4. 【悩みその1】Go言語のソースファイルをどう分割すればいいの?(クラスの概念ないし…)

Javaのソースファイル構成とGo言語のソースファイル構成を比較して考えてみました。

4-1. Javaのソースファイル構成

Javaプログラマが上記のクラス図を見てソースファイル構成を決めると、大抵1クラス1ファイルにすると思います。

ファイル構成
└─src
    ├─animal                         // javaのパッケージ単位フォルダ
    │      Animal.java               // javaのクラス単位
    │      Cat.java
    │      Dog.java
    │
    └─main
            Main.java

4-2. Go言語のソースファイル構成

 Javaのソースファイル構成と同じ構成です。今回はあえて同じにしています。
あえての理由は、"「悩みその1」まとめ"で。

ファイル構成
└─src
    ├─animal                         // goのパッケージ単位フォルダ
    │      Animal.go                 // goにはクラスの概念がない
    │      Cat.go
    │      Dog.go
    │
    └─main
            Main.go

 Go言語にはクラスの概念がないので、3つ(Animal.go, Cat.go, Dog.go)のソースファイルを分割する必要はありません。(ただし、パッケージは同じでないとダメ)

 例えば、次のように1ファイルにまとめるても文法的にはOK。(あくまで文法的にはです。。。)

ファイル構成
└─src
    ├─animal 
    │      Animal_Cat_Dog.go   // 1つのソースファイルにまとめても文法的にOK
    │
    └─main
            Main.go

4-3. 「悩みその1」まとめ

「Go言語のソースファイル分割をどうすればいいの?(クラスの概念ないし…)」に対する自分なりの答えは以下となりました。

  • Go言語のソースファイルはGitHubで管理しやすい(競合しない細かい)単位にする。
  • 似て非なる機能(今回の例だと、動物の種類(サルとかキジとか))が増えることがあらかじめわかっていれば、その機能単位でソースファイルを作成する。
  • Go言語には継承がないので、パッケージフォルダに基底となるソースファイル(今回の例だと”Animal.go”)を1つ作成する。

※ ベストなやり方を模索しているので皆さんコメントお願いします。(^O^)/

5. 【悩みその2】 Go言語では継承(extends)の概念がないので共通処理(抽象メソッド)をどうやって実現すればいいの?

5-1. 共通処理(抽象メソッド)

まずはJavaのソースを見てみましょう。
動物は鳴く(bark)という振舞いを抽象メソッドで実装してます。

Animal.java
package animal;

public abstract class Animal {
    public abstract void bark();
}

そして、Go言語のソース。
Go言語は、抽象メソッドはありませんので、代わりにインターフェースで鳴く(bark)という振舞いを実装しています。

「動物という抽象的な概念(クラスに代わるもの)はない」です。。
※ ファイル名のAnimalはどこにも影響ありません。

Animal.go
package animal

type Barker interface {
    Bark(string)
}

あと、先頭が大文字になるとpublicスコープになるみたいです。

5-2. 具象オブジェクト(Cat)

こちらもJavaソースから。
Catクラスはネコの鳴き声(voice)を持ち、
ネコが鳴く(bark)振舞いを持っています。

Cat.java
package animal;

public class Cat extends Animal {
    private String voice = "にゃーにゃー";

    public void bark() {
        System.out.println(this.voice);
    }
}

次にGo言語。

 Go言語はクラスの概念がないので、ネコを構造体(struct)で定義し、ネコ型のデータ型(type)として宣言します。しかし、データ型はJavaクラスのように値("voice=にゃーにゃー")を持つことができません。

 ここで登場するのが、Javaコンストラクタ風のメソッドNewCat()です。このメソッドでデータ型(Cat)のインスタンスを作成する時に値(”にゃーにゃー”)を持たせます。

 そして、Animal.goで定義したインターフェースで宣言したBark()を実装します。ここでポイントとなるのが、レシーバーです。
 レシーバーとは「データ型に対してメソッド定義されたもの」です。ここではデータ型(type Cat struct)のメソッド(Bark())ですね。

Cat.go
package animal

import (
    "fmt"
)

type Cat struct {    // ネコを構造体(struct)で定義
    voice string
}

func NewCat() *Cat {  // Javaコンストラクタ風のメソッド
    return &Cat{"にゃーにゃー"} 
}

func (c Cat) Bark(){  // レシーバー
    fmt.Println(c.voice)
}

5-3. 具象オブジェクト(Dog)

Cat と同じなので省略。

5-4. Mainオブジェクト

最後にMainです。こちらもJavaから。
Cat,Dogの具象オブジェクトのインスタンスを作成し、
インターフェース(Animal)で定義したメソッド(bark)を呼び出しています。

Main.java
package main;

import animal.Cat;
import animal.Dog;
import animal.Animal;

public class Main {
    public static void main(String[] args) {
       Animal c = new Cat();
       Animal d = new Dog();

       c.bark();
       d.bark();
    }
}

次にGo言語。
 具象オブジェクト(Cat や Dog)からインタフェース(Baker)に変換するところも、基本的にJavaと同じで分かりやすいですね。
 
```go:Main.go
package main

import (
"animal"
)

func main(){
var c animal.Barker = animal.NewCat()
var d animal.Barker = animal.NewDog()

c.Bark()
d.Bark()

}
```

まとめ

 同じ設計をGo言語とJava言語で実装してみることで、Java言語の設計(クラス設計)をGo言語に適用することはできそうです。ただし、実装するときには言語仕様の違いがあるので、Java言語のクラス設計をGo言語に適用するには、今回のように実装パターンを覚える必要がありそうです。

 Java言語は継承(extends)や実装(implements)によりクラス間に制約を与え、それぞれの役割を明確にすることができるので、大人数(大規模)で実装するのに適しているかもしれません。一方、Go言語は制約がゆるいのでオブジェクト間の依存度が低いため、頻繁に改修されるようなアプリケーションに向いていると感じました。

参考URL