Factory Method

目的 FactoryMethod はインスタンスの生成に関するパターンです。 このパターンの目的は、インターフェイスに対する実装の生成・代入局面を隠し、疎結合を完成させることです。 概要 インターフェイスを用いてポリモーフィズム(多様性)を実現したいとします。また、利用側にインターフェイスだけを見せることで疎結合も実現したいとします。こうすることで、多様な実装の切り替えに対応することができ保守性を高められるからです。さて、この時気をつけなければならないのが、ポリモーフィズム適用時の隙(仮称)です。 ポリモーフィズム適用時の隙 あるサスペンスドラマで犯人が怪人の仮面をかぶっていたとします。犯人はAさんかも知れない。しかしBさんかも知れない。AさんにもBさんにも第1の犯行が可能だ。そして難解なことに第2の犯行も可能だ。 ポリモーフィズムとは「多様性」です。仕事を決めてやり方を決めない「インターフェイス」によってその多様性を担保します。多様性はインターフェイスを満足させられる実装であれば、どれでも実現可能…という仕組みで担保します。この例で説明するならば、犯人はAさんでもあり得るしBさんでもあり得るのという部分です。そして「疎結合」というのは、利用者は何を使っているのかを感知していないという構造で担保します。この例で説明するならば、目撃者は仮面をつけて行った2つの犯行しか見ていないという部分です しかし、AさんかBさんの何れかが仮面を被るその瞬間を見られていたら? 多様性はいとも簡単に崩れ去ります。ですから犯人は仮面を被る瞬間を見られてはいけません。仮面を付けた状態で物陰から登場するのです。この、物陰の役割を果たす存在こそが Factory Method、物語や設計をミステリアスにさせるエッセンスです。 以上の例からもわかるように、Factoryと聞くと生成プロセスに注目してしまいがちですが、FactoryMethodの真の目的は生成物をインターフェイスの型で提供することで、その実体を隠しきることなのです。 例えば、良く見かける生成プロセスを隠しきれていないコードを提示します。このコードは生成のプロセスを完全に隠蔽してはいません。そのため当該スコープにおける多様性は担保出来ていません。これが Factoryを使わないコードの限界です。 var e tech.Engineer = &impl.Onda{} e.Program() e.Test() e.Publish()  ではこのコードが問題であるかというと、一概にそうとも言えません。何故なら代入の瞬間以外はインターフェイスに依存していないコードであることは担保できているので、本当にポリモーフィズムが必要になったその後に、簡単にポリモーフィズムを完成させることができるからです。 [余談] ポリモーフィズムとインターフェイス分離の原則 先の例に関してですが、名探偵が、様々な状況から推測して犯人を絞り込んでゆく様は、インターフェイスの責務を増やしてゆく過程に相当します。犯行日の深夜に動けた人、凶器を調達できた人、そして被害者の行動を知っていた人。最終的にインターフェイスを満足させ続けた人が犯人です。この様にして重すぎるインターフェイスの責務は実現クラスの枯渇を招きます。謎を解き明かしたい探偵の技術としては良いものですが、抽象化の力を振るいたい設計者として悪しきものです。この件に関する詳細は「インターフェイス分離の原則」で説明したいと思います 実現 次のシナリオでは、"Hello"という文字列を格納するため、Storageを生成しています。最初のテストケースでは factoryを用いてその実体を隠蔽できていますが、二番目のテストケースでは factoryを用いないため、その実体を隠しきれていません。この瞬間に疎結合という価値は失われてしまいます。 package factory_method_pattern import ( "testing" "github.com/stretchr/testify/assert" "github.com/koooyooo/go-design-pattern/factory_method/factory" ) // FactoryMethodを適用した場合 // Storageの実体を知ることなく利用できる (疎結合) func TestFactoryMethod(t *testing.T) { // Factory経由で実体を隠蔽 s := factory.CreateStorage() err := s.Store([]byte("Hello")) assert.NoError(t, err) } // FactoryMethodを適用しない場合 // Storageの実体を知ってしまう (密結合) func TestNonFactory(t *testing.

Singleton

目的 Singletonの目的は一つだけインスタンスを生成し、それを使い回すことです。 パターンの実装としては「一つしか生成できなくする」ための制約を作り込む形となります。 OOPとインスタンス数 オブジェクト指向(OOP)の世界では、特別な理由がない限り現実世界のモノの数と、インスタンスの数を一致させます。豚が3匹いるなら Pigインスタンスを 3つ生成します。同様に車が1000台なら Carインスタンスを1000個生成します。この理由は、複製したインスタンスの数だけ固有の情報を格納できるからです。現実世界のものが増えたのならば、それを管理するオブジェクトも同じだけ増やすのが基本です。オブジェクト指向言語が現実の世界や概念の構造を雄弁に表現できるのは、この状態の複製が簡単に(new Car() or Car{})実現できるからという点が大きいのです。 さて、オブジェクトと言えば状態と振る舞いを持ちます。この両者で特に大きな進化をもたらしたのはどちらでしょうか?実は、インスタンスの増加で増えるのは状態だけです。状態はクラス(struct)定義時のメソッドの宣言から変わることはありません。インスタンスが複製されてもその振る舞いが同じ様にコピーされるだけです。唯一の振る舞いの変化が許される部分は、そのインスタンスの状態に依って振る舞いが変わるロジックが定義されている時です。しかし、オブジェクト指向言語によって主役の座に位置するのが状態であることに変わりはありません。 この常識を根底から覆すのが Singletonです。何故ならその状態を1つで良い…と言い張るのですから。しかし Singletonについて深く考察してゆくと、そこにもまた OOPの本質に迫る側面があることに気付くはずです。 一つだけインスタンスを生成してそれを使い回すと何が良いのでしょうか。すぐには思い浮かばないかも知れませんが、これが活きる局面は数多くあります。 1. 数の限定 最初に紹介するのは、本当に数が限られているものをそのとおりに表現するものです。1つしかない物を厳格に表現するなら Singletonは良い選択です。例えば日本に政府は一つしかありません。つまり以下のコードが成立します。 gov := jpgov.Instance() ディズニーランド内にミッキーマウスは複数存在しないという話を聞いたことがあります。それが本当なら次のコードも成立します。 m := disney.MickyInstance() 次にシステム的な面で考えてみます。データベースを管理するマネージャが1つしかないのであれば、以下の表現が可能です。インスタンスは関数経由で取得しても良いですし、初期ロード時に生成しても構わないなら変数として用意しておいても良いでしょう。 // 関数で取得(実装上の選択肢は広がる) dbMgr := database.ManagerInstance() // 変数で取得 dbMgr := database.ManagerInstance 2. 状態の共有 Singletonが公開されるということは、単一の状態も公開されるということです。一般的にSingletonの参照はグローバルに提供されることが多いため、どこからでもアクセス可能な単一の状態という便利なものが手に入ります。例えば、Util系のクラス(package)は振る舞いという観点で単一のものを提供しますが、Singletonでは状態という観点で単一のものが提供出来るわけです。 次の例は、Observerパターンで通知を行う局面です。システムの各所からこのObserverに通知が入ります。このObserverは Singletonとして常に単一のインスタンスが参照されるので情報を集中的に管理できています。各所で別々のインスタンスを生成してしまったのでは集中管理になりません。 func (s Service) report() { o := observer.Instance() o.reportAccess(1) } 一つしか無いというのは、一箇所で情報を管理できるという強みがあるのです。総理大臣や大統領はいくら忙しくても2名以上にしません。責務あるインスタンスに情報を集約させるための手段としても Singletonは有効なのです。 3. 参照の取得 上の例をもう少し使います。通常、Observerのインスタンスが必要なら上位の生成元から延々と引数で引き渡す必要があります。しかし、Singletonではその必要がありません。Singletonの生成部分はグローバルな関数(Javaなら staticメソッド, Golangなら グローバル関数)ですので、調達は簡単です。引数をシンプルに保てますのでその点では設計上の長所となります。 一方で、グローバルな関数に触れるというのは、その処理が副作用を持つということです。単純な長所だけではないので、この部分には気をつける必要があります。余談ですが、この解決策としてDI(Dependency Injection: 依存性の注入)という仕組みが存在し、DIが導入の際には Singletonは DIコンテナ側で実現されます。これがSingletonの完成形かもしれません。 type Service struct { // 注入される依存性 o *observer.