Go 言語で Factory Method Pattern を構成できるか

少し前に面白い記事を見かけたのだが

「これって factory method pattern じゃないよね」と思いつつ「Go 言語には「継承」がない」ことを説明する好例だと気付いたので今回の記事を書いてみた。

Factory Method Pattern とは

まずは factory method pattern の解説から。 以下にクラス図を示す。

Product は抽象クラスで ConcreteProductProduct の実装クラスである。 Creator クラスの factoryMethod() メソッドは Product インスタンスを返す抽象メソッドで,サブクラスの ConcreteCreator で実装されている。

Creator クラスを利用する側はサブクラスの ConcreteCreatorCreator クラスとして生成し anOperation() メソッドをキックする。

ポイントは anOperation() メソッドが実装メソッドで,内部では factoryMethod() メソッドが呼ばれている点である。 ただし Creator インスタンスの実体は ConcreteCreator クラスなので, anOperation() メソッドから呼ばれるのはオーバーライドされた ConcreteCreator のほうの factoryMethod() であることが期待される,というわけだ。

本来なら Creator インスタント生成時にどの ConcreteProduct インスタンスを(Product クラスとして)使うのか判断すればいいのだが(これが factory pattern),それだと ConcreteProduct クラスが増えるたびに Creator クラスも修正しなければならず管理が煩雑になる。

そこで ConcreteProduct クラスと一対一に対応する ConcreteCreator クラスをつくり,その中でどの ConcreteProduct インスタンスを(Product クラスとして)使うのか判断している,つまり「インスタンス作成をサブクラスにまかせる」のである。

Go 言語には「継承(Inheritance)」がない

しかし,このデザイン・パターンは Go 言語では使えない。 何故なら「Go 言語には「継承」がない」からである。

汎化と構造的部分型(Structural Subtyping)

Go 言語ではクラス間の「汎化」関係を実装する手段として interface 型を持っているが,これは「振る舞い」のみを定義する。 先程の Creator クラスのように抽象メソッドと実装メソッドをひとつのクラスの中に混ぜ込むことはできない。 Interface のような型を一般的には「構造的部分型」と呼ぶらしい1

埋め込み(Embedding)と委譲(Delegation)

もうひとつ Go 言語には型を構造体のフィールドとして埋め込む機能がある。 たとえば

type parent struct{}

という型があるとして

type child struct {
    parent
}

とすれば child 型は parent 型の属性や操作をそのまま使うことができる。 こんな感じ。

package main

import "fmt"

type parent struct{}

func (p parent) Say() string {
    return "I'm parent type."
}

type child struct {
    parent
}

func main() {
    p := parent{}
    c := child{}
    fmt.Println(p.Say())
    fmt.Println(c.Say())
    //Output:
    //I'm parent type.
    //I'm parent type.
}

これで child 型は parent 型を(擬似的に)継承しているように見える。 更に child 型に対して

func (c child) Say() string {
    return "I'm child type."
}

という関数を追加すれば

package main

import "fmt"

type parent struct{}

func (p parent) Say() string {
    return "I'm parent type."
}

type child struct {
    parent
}

func (c child) Say() string {
    return "I'm child type."
}

func main() {
    p := parent{}
    c := child{}
    fmt.Println(p.Say())
    fmt.Println(c.Say())
    //Output:
    //I'm parent type.
    //I'm child type.
}

という感じになり child 型の Say() 関数が parent 型の Say() 関数をオーバーライドしているように見える。 ところが parent 型に

func (p parent) Speek() {
    fmt.Println(p.Say())
}

という関数を追加し,これを使って喋らせてみると

package main

import "fmt"

type parent struct{}

func (p parent) Say() string {
    return "I'm parent type."
}

func (p parent) Speek() {
    fmt.Println(p.Say())
}

type child struct {
    parent
}

func (c child) Say() string {
    return "I'm child type."
}

func main() {
    c := child{}
    fmt.Println(c.Say())
    c.Speek()
    //Output:
    //I'm child type.
    //I'm parent type.
}

となり Speek() 関数はあくまでも parent 型の Say() 関数を呼び出していることが分かる。

実は Go 言語の埋め込みフィールドは「継承」ではなく「委譲」として機能する。 したがって C++ や Java の仮想関数のようにメソッドがオーバーライドされることもない。 型の中で関数名が被った際の暗黙の優先順位は決められているためオーバーライドしているように見えるだけなのである。

これを先程の factory method pattern のクラス図に当てはめて考えると Go 言語で無理やり実装しようとしても

という感じになり,そもそも Creator を抽象クラスに(polymorphic に)できないし,サブクラスの ConcreteCreator から anOperation() メソッドを呼び出しても内部で呼び出される factoryMethod() メソッドは Creator クラスのものということになる。

「継承」できないなら「注入」すればいいじゃない

さて,どうしようか。 とりあえずコードを書きながら考えてみよう。

最初に紹介した記事を参考にしつつ,まずは Product に相当する interface 型を以下のように定義するところから始めようか。

//Human is Product class of factory method pattern
type Human interface {
    SelfIntroduction() string
}

続いて ConcreteProduct に相当する型も定義する。

//NamedHuman is parent class with name property
type NamedHuman struct {
    MyName string
}

func (nh NamedHuman) Name() string {
    if len(nh.MyName) == 0 {
        return "〈名無し〉"
    }
    return nh.MyName
}

//Man is Concrete Product class of factory method pattern
type Man struct {
    NamedHuman
}

func (hm Man) SelfIntroduction() string {
    return hm.Name() + "だぜ!"
}

//woman is Concrete Product class of factory method pattern
type Woman struct {
    NamedHuman
}

func (hm Woman) SelfIntroduction() string {
    return hm.Name() + "よ!"
}

ちょっと分かりにくいかも知れないが,クラス図にすると

となっていて Man 型および Woman 型が(Human 型に対する) ConcreteProduct クラスに相当するのが分かると思う。 ちょろんと動かしてみよう。

func NewMan(name string) Human {
    m := &Man{}
    m.MyName = name
    return m
}
func NewWoman(name string) Human {
    w := &Woman{}
    w.MyName = name
    return w
}
func main() {
    h1 := NewMan("太郎")
    h2 := NewWoman("花子")
    fmt.Println(h1.SelfIntroduction())
    fmt.Println(h2.SelfIntroduction())
    //Output:
    //太郎だぜ!
    //花子よ!
}

問題なく動作することが確認できた。

ここまで書いたコードを眺めてみると NewMan() および NewWoman() 関数が factoryMethod() メソッドと似た機能を有していることが分かる。 じゃあ,これらの関数を実行時に Creator クラスに埋め込むように構成すればいんじゃね?

さっそく書いてみよう。

//Speaker is Creator class of factory method pattern
type Speaker struct {
    createHuman func(string) Human
}

func (s Speaker) Speech(name string) {
    hm := s.createHuman(name)
    fmt.Println(hm.SelfIntroduction())
}

createHuman は関数型のメンバ変数で func(string) Human のインタフェースを持つ関数を表す。 これはすなわち NewMan() および NewWoman() 関数のことである。

それでは実際に動かしてみよう。

func NewSpeeker(f func(string) Human) *Speaker {
    return &Speaker{createHuman: f}
}

func main() {
    s1 := NewSpeeker(NewMan)
    s2 := NewSpeeker(NewWoman)
    s1.Speech("太郎")
    s2.Speech("花子")
    //Output:
    //太郎だぜ!
    //花子よ!
}

よーし,うむうむ,よーし。 クラス図にするとこんな感じだろうか(factory method pattern のクラス図と比較してみよう)。

これってだいぶ不格好だけど,まさしく依存(オブジェクト)の注入(dependency injection pattern)だよね2

ていうか Go 言語プログラマは error 型や標準パッケージの io.Reader などを通じて息をするように「依存の注入」を行っているわけで,「継承できないなら注入すればいいじゃない」という感じで,自国語に得意なパターンへ持ち込むのが得策だと思う。

ブックマーク

参考図書

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan, Brian W. Kernighan
丸善出版
評価 

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。

reviewed by Spiegel on 2018.10.19 (powered by Amakuri)

増補改訂版 Java言語で学ぶデザインパターン入門
増補改訂版 Java言語で学ぶデザインパターン入門
結城 浩
SBクリエイティブ
評価 

結城浩さんによる通称「デザパタ本」の Kindle 版。意外にも Java 以外でも応用できる優れもの。

reviewed by Spiegel on 2018.12.7 (powered by Amakuri)


  1. Interface 型の性質を示すものとして duck typing がよく挙げられる。私も以前はそう思っていたが duck typing は動的型付き言語で型を決定する手法(のひとつ)を指すのだそうだ。もちろん Go 言語は静的型付き言語なのでこれに当てはまらない。じゃあ Go 言語の interface 型は何かというと「構造的部分型」と呼ぶのが正しいようだ。 [return]
  2. 私は dependency injection を「依存性の注入」と訳すことに懐疑的だという主張に同意します。 [return]