Go言語がわかってきたので、簡単なシングルトンを作ってみました。

singleton.go

package singleton

type Singleton struct {
	resources map[string] string
}

var instance *Singleton = nil

func newSingleton() *Singleton {
	ins := new(Singleton)
	ins.resources = make(map[string] string)
	return ins
}

func Instance() *Singleton {
	if instance == nil {
		instance = newSingleton()
	}
	return instance
}

func (this *Singleton)Resource(key string) string {
	return this.resources[key]
}

func (this *Singleton)SetResource(key, value string) {
	this.resources[key] = value
}

func (this *Singleton)Clear() {
	this.resources = make(map[string] string)
}

Go言語の慣例では、複数の単語から成る名前をつけるときはアンダースコアを使わずに、MixedCapsまたはmixedCapsのように単語の先頭だけ大文字を用います。そして名前の先頭の文字が大文字であれば、パッケージ外に公開することができます。

このシングルトンの例では、Singleton型は外部に公開していますが、メンバは公開していません。Singleton型のメンバへは、公開メソッド(Instance(), Resource(), SetResource(), Clear())によってのみ、アクセスすることが許されます。

使用例は、次のようになります。

package main

import "./singleton"

func main() {
	println(singleton.Instance().Resource("hoge"))	
	singleton.Instance().SetResource("hoge", "fuga")
	println(singleton.Instance().Resource("hoge"))	
	singleton.Instance().Clear()
	println(singleton.Instance().Resource("hoge"))	
}

Go言語はクラスや継承等の概念はないですが、パッケージや構造体をうまく使い分けて設計することで、ピュアなオブジェクト指向っぽいプログラミングが可能ですね。


今回はポリモーフィズムの例として、fmtパッケージのPrintlnメソッドを簡単に紹介したいと思います。

まず、Printlnメソッドの仕様です。

func Println(a ...interface{}) (n int, errno os.Error)

引数の宣言部分に注目して下さい。...は可変長引数を意味し、次のinterface{}が型になります。interfaceではメソッドを宣言していないため、どんな型の変数でも受け取ることが出来ます。

試しに、次のコードを実行してみて下さい。

package main

import "fmt"

func main() {
	i := 1234
	s := "hoge"
	fmt.Println(i, s)
}

実行結果は、次のようになります。

1234 hoge

まさに期待通りの結果になっていますが、種を明かすとfmt.Printlnは引数で渡されてくる変数が、次のインタフェースを実装されていることを期待しています。

type Stringer interface {
        String() string
}

Printlnは受け取った値の型検証を行い、期待するインタフェースが実装されていたら、変換してインタフェースのメソッドから値を取得しています。

つまりはこんな感じです。

...
s, ok := a.(Stringer)  // Go言語の型アサーション
if ok {
        result = s.String()
} else {
        result = defaultOutput(v)
}
...

変数.(型)はGo言語の型アサーションで、この例では変数aがStringer型であれば、変換した結果をsに代入し、okはtrueとなります。変換できない場合は、okはfalseとなります。

試しに、新しく構造体を定義して、その構造体の変数をPrintlnに渡してみます。

package main

import "fmt"

type value struct {
	name string
	value string
}

func main() {
	fmt.Println(&value{name:"hoge", value:"fuga"})
	// &value{name:"hoge", value:"fuga"}は、次のように書くのと同じです。
	// v := new(value)
	// v.name = "hoge"
	// v.value = "fuga"
}

実行結果は、次のようになります。これは期待通りなんでしょうか?!

&{hoge fuga}

期待通りの結果を得るために、次のメソッドを追加して再実行します。

func (this *value)String() string {
	return "value{name:\"" + this.name +"\",value:\"" + this.value + "\"}"
}

実行結果は、次のようになります。バッチリですね。

value{name:"hoge",value:"fuga"}


Go言語は、オブジェクト指向言語ようにクラスや継承によるクラス階層の概念を持ちませんが、インタフェースの概念は持っています。 Go言語のインタフェースはJavaのインタフェースに類似していますが、インタフェースを実現するための対象が異なります。

Java言語のインタフェースの場合は、次の例のようにインタフェースで宣言したメソッドをクラスに対して強制的に実装させることで、同じインタフェースを実装しているクラスのインスタンスに対して、インタフェースで宣言しているメソッドで同じように呼び出すことができるようになります。

interface Speaker {
	void speak();
}

class IntSpeaker implements Speaker {
	@Override
	public void speak() { ... }
}

class StringSpeaker implements Speaker {
	@Override
	public void speak() { ... }
}

class NoImplSpeaker {
	public void speak() { ... }
}
...
IntSpeaker intSpeaker = new IntSpeaker();
StringSpeaker stringSpeaker = new StringSpeaker();
NoImplSpeaker noImplSpeaker = new NoImplSpeaker();

Speaker speaker;
speaker = intSpeaker;
speaker.speak(); // OK
speaker = stringSpeaker;
speaker.speak(); // OK
speaker = noImplSpeaker; // ERROR(当然ですが、、、)
...

Go言語のインタフェースの場合は、Java言語とだいたい同じ考え方ですが、インタフェースで宣言したメソッドの実装対象が型(構造体を含む)になります。

まず、次のようにインタフェースを宣言します。インタフェース内のメソッドは複数宣言することができます。

type インタフェース名 interface {
	メソッド名(引数)戻り値
}

型にメソッドを追加する場合は、次のようにメソッドの宣言を行います。メソッド名の直前に括弧でメソッドが結びつく型を宣言します。その型のことをGo言語ではレシーバと呼びます。

func (レシーバ変数 レシーバとなる型)メソッド名(引数) 戻り値 {
...
}

ここまでの説明で、Go言語のインタフェースの宣言と型に対するメソッドの宣言について説明しましたが、型に対するメソッドの宣言は特にインタフェースを意識していないことがわかったと思います。

Go言語では、インタフェースで宣言されているメソッドがすべて型のメソッドとして宣言されている場合にのみ、インタフェースを実装していると判断します。次の例では、text型はhogeインタフェースを実装していると判断できないため、コンパイル時にエラーが発生します。

type hoge interface {
	foo()
	bar()
}

type text string
func (this text)foo() {
...
}

var h hoge
vat t text = "fugafuga"
h = t // ERROR(barメソッドが実装されていないため、hogeインタフェースを実装していると判断できない)

最後にGo言語のインタフェースを利用したポリモーフィズムの例を紹介します。

speakerインタフェースを実装したintSpeaker型とstringSpeaker型の変数をspeaker型の配列として初期化し、配列から変数を取り出してspeakメソッドをコールしています。ちなみに配列初期化時のintSpeaker(1)のような型(値)という書き方は、Go言語の変換という仕様で、Java言語やC言語のキャストと同じです。

package main

import "fmt"

type speaker interface {
	speak()
}

type intSpeaker int
func (this intSpeaker)speak() {
	fmt.Printf("%d\n", this)
}

type stringSpeaker string
func (this stringSpeaker)speak() {
	fmt.Printf("%s\n", this)
}

func main() {
	for _, item := range []speaker{intSpeaker(1), stringSpeaker("hoge")} {
		item.speak()
	}
}


最近、Go言語のネタが多いですが、気にせずに行きましょう。

ちょっとGo言語のコードを書くにも、エディタがしっくりしなくて困っていましたが、「m92oの技術日記」にGo言語のリポジトリのmiscにgo-mode.elが含まれているとの情報がありましたので、早速試してみました。

筆者はmacportsでGo言語をインストールしたため、macportsが利用するソースフォルダ(デフォルトは/opt/local/src/go)を覗いてみました。m92oさんの情報の通り、miscフォルダの中にモードファイルが含まれていました。(ローカルにソースコードをチェックアウトしていない場合は、リポジトリ(https://go.googlecode.com/hg/misc/emacs/)から直接取得することも出来ます。)

emacsの他にもvimやxcodeのモードファイルも用意されているようです。

筆者は普段Aquamacsを利用しているため、Aquamacsにモードファイルを追加することにします。追加手順は次の通りです。

  1. ~/Library/Application Support/Aquamacs Emacs/以下にモードファイル(go-mode.el,go-mode-load.el)をコピーする
    今回は、~/Library/Application Support/Aquamacs Emacs/lispというフォルダを作成し、モードファイルをコピーしました。
  2. Preferences.elにGo言語のモードを追加する 
    ~/Library/Preferences/Aquamacs Emacs/ Preferences.elに次のコードを追加します。
    ;; Go Mode
    (require 'go-mode-load)

以上の設定後、AquamacsでGo言語のソースコードを開くと次のようになります。