kanizaのブログ

Apple 関連やコンピュータ、映画、音楽関連や家族のことなど、興味があることを書きます。

Cocoa勉強会関西で Singleton を思いきりdisってきた

昨日(6/1)、はてな京都オフィスで開催されたCocoa勉強会関西で「ワタシは Singleton がキライだ」というタイトルで発表してきた。内容について興味のある方はスライドを SlideShare に載せたのでそちらを見てください。

(2013/6/8 追記: この発表で使ったサンプルとだいたい同じものを GitHub で公開しました。CoreDataBooks ではなく、Core Data アプリのテンプレートをベースにしたものになっています)

 

時間はだいたい30分くらいが基本なんだけど、今回はちょっと気合いを入れて45分枠をもらった。

今回発表したようなテーマには、個人的には10年近く前に Java プログラマをやっていた頃から取り組んでいて、当時からはてなダイアリーに書いたりしてた(この記事とかこの記事とか)。Java 方面では Spring やら Google Guice といった DI コンテナが普及するなど一定の解決を見たのかなぁと思っていた。Java 方面の情勢は5年ほど前からよくわかってないけど。

Cocoa / Objective-C での DI コンテナについては認知度も含めてまだまだこれからなのかなーという印象。

Q&Aでいただいた2つの質問について、その場での回答はいまいちだっので補足しておく。

Q. サブクラスは必要になるの?

質問は、僕がデモに使った CoreDataBooks を Typhoon ベースにしたものが、もともと AppDelegate でやっていた Core Data スタックの初期化処理を、新しく作った NSManagedObjectContext とかのサブクラスのイニシャライザに実装していた件について。つまり、オリジナルにはないNSManagedObjectContext と NSManagedObjectModel と NSPersistentStoreCoordinator のサブクラスを作ってあるというわけ。

いま理解している範囲だと、TyphoonAssembly ではコンポーネント情報を「定義」としてしか記述できないっぽい。ここで直接インスタンスを登録できたりすれば、自分で生成してカスタマイズしたインスタンスをコンテナに入れて、他のコンポーネントに注入できるはず(Java ではそれができるコンテナもある)。

Typhoon ではできないっぽいので、コンテナが生成したときに呼ばれるイニシャライザに細かい初期化処理を記述すべくサブクラスを作っている。もちろんコンポーネント間の接続という部分はコンテナがやってくれるけど、たとえば保存形式をどうするとかっていう調整部分ですな。

発表の時は「だからいまんところサブクラスはいると思ってます」と答えたけど、よく考えたら Objective-C なんだから NSManagedObjectContext そのものに別のイニシャライザをカテゴリで追加することもできるんだよね。それでもよさそうだ。Java 脳になってたらしい。

質問の意図としては「そうやってクラスが増えてしまうのは嬉しくないかもなー」というのがあったのかなと想像している(あくまで想像)。そして、それはカテゴリにしても同じような疑問があるような気がしている。

たしかにクラスとかカテゴリは増えると思う。そしてそれがイマイチな感じがするのもすごくよくわかる。

ただ、ソフトウェアのコンポーネントという視点で見たときに、そのコンポーネントの初期化処理はそのコンポーネント自身が知っていて、他の人は関知しないというのもこれはまたアリな価値観ではないかなと思っている。初期化処理なんだからイニシャライザでやろうよ、とも思ったり。そのためにクラスやらカテゴリが増えるのなら、しゃーないか、と。

Q. 設計上、離れた場所にあるコンポーネントの参照が欲しくなったときにはやっぱりコンテナから取得するの?

これも回答ではいろいろ話したけど、われながらわかりにくかったと思う。

質問のイメージとしてはこういう感じじゃないかな。

"Foo クラスを実装しています。Foo のある処理で Bar オブジェクトを生成します。それには Buzz が必要なんだけど、Foo は Buzz を知らない"

この時に、Foo はコンテナ経由で Buzz を取得して、Bar を生成するのかどうか。だとしたらコンテナはどうやって取得するのか。それはもちろんシングルト…いやいやいや、それじゃあ Singleton をdisってる場合じゃなくなるわけです。

おすすめできる解決方法としては2パターンある。

1つは、Foo に必要なんだったら最初から Bar をコンテナで注入してやる。これで生成する必要はなくなる。ただ、Bar の生成をどうしても遅らせたかったり、複数回生成する必要があったりするとこれは使えない。

そういう場合はもう1つのパターンで、Bar を生成する BarFactory クラスを作って、BarFactory を Foo に注入する。BarFactory は Buzz を注入されている。Foo は Buzz を知らなくても、BarFactory で Bar を好きなだけ生成できる。

おすすめできない第3のパターンは、Bar に Buzz を注入しておいて、それを使って Bar を生成する方法。これでも目的は達成できるけど、Foo には本来必要ない Buzz への依存ができてしまう。Buzz だけならまだしも、Bar の生成に 4 つくらいオブジェクトが必要になったらそれを全部 Foo に持たせるのはかなりダルそうだよね。生成に必要な情報は専門の Factory オブジェクトに集約する。

Q&Aの補足はこんなとこかな。

僕自身は Java プログラマ時代に DI コンテナで Singleton とオサラバしてから、オブジェクト指向プログラミングについて見える世界がガラっと変わったので、そーいう感覚で Cocoa のコードも書きたい気持ちがあるんだよね。Objective-C の動的性質をうまく使えば DI コンテナなんてややこしいものがなくても問題解決できるのかもしれないとも思ってたけど、どうもそうでもなさそうなので DI コンテナを本格的に調査してみている。

今回は Singleton をdisるという名目で、Singletonへの問題意識はあるていど共有できたのではないかと思う。でもその解決方法としての DI はちょっとやそっとでは伝わらないし、僕自身 Cocoa で本当に有効かどうかの確信もないので、引き続き取り組んでいくつもり。