DI可能なSingleton
Singletonを使うとユニットテストが難しいよねという話。
実際は自分流のSingletonの書き方の変遷をまとめただけ。
最初の頃、Singletonは次のように書いていた。
class Foo { public: void someFunc() { // do something } static Foo* getInstance() { if(!instance_) { instance_ = new Foo(); } return instance_; } private: Foo() {} // コンストラクタ static Foo* instance_; }; Foo* Foo::instance_ = NULL; int main() { Foo* foo = Foo::getInstance(); foo->someFun(); }
この書き方の問題点
- newしているのにdeleteしていない。
- そもそもdeleteするタイミングが掴めない。*1
- staticメンバ変数使うのが気に入らない。
というわけで静的ローカル変数を使うようになった。
class Foo { public: void someFunc() { // do something } static Foo& getInstance() { static Foo instance_; return instance_; } private: Foo() {} // コンストラクタ }; int main() { Foo& foo = Foo::getInstance(); foo.someFun(); }
この書き方の利点
- インスタンスの寿命について気にする必要がない。
- 静的メンバ変数がなくなって気分的にすっきり。
問題点
- 使うときにいちいち参照を取る必要がある。
- もしくは毎回 Foo::getInstance().someFunc() となって見栄えが悪い。
それを踏まえて、最近の(自分の中での)流行のやり方はこう。
class Foo { public: static void someFunc() { getImpl().someFunc(); } private: Foo() {} // コンストラクタ static Foo& getImpl() { static FooImpl impl_; return impl_; } }; class FooImpl { public: void someFunc() { // do something } }; int main() { Foo::someFun(); }
この書き方の利点
- 毎回 Foo::getInstance() と書かなくてもいい。
問題点
- FooImplへの依存度が高い
- Fooを呼び出す他のクラスのテストを行う場合、FooImplをモックなどに置き換えられない。
- インスタンスをキャッシュできない。(毎回 Foo::getImpl() を呼ぶことになる)
- おそらく Foo::getImpl() を呼ぶコスト <<<<< 他のボトルネック なので気にしない方向で。
さて、呼び出しが簡潔になったのはいいが、このままではFooを呼び出すクラスのテストが面倒くさい。
そこで、Dependency Injection(依存性注入)ができるSingletonを考えてみた。
class Foo { public: static void someFunc() { getImpl()->someFunc(); } static void setImpl(IFooImpl* impl) { impl_ = impl; } private: Foo() {} // コンストラクタ static IFooImpl* getImpl() { if(!impl_) { static FooImpl impl; impl_ = &impl; } return impl_; } static IFooImpl* impl_; }; IFooImpl* Foo::impl_ = NULL; class IFooImpl { public: virtual void someFunc() = 0; protected: ~IFoo() {} }; class FooImpl : public IFooImpl { public: virtual void someFunc() { // do something } }; class MockFooImpl : public IFooImpl { public: virtual void someFunc() { // do something like FooImpl } }; int main() { MockFooImpl mock_impl; Foo::setImpl(mock_impl); // MockFooImpl::someFunc()が呼ばれる // まだFooImplのインスタンスは作られない Foo::someFunc(); Foo::setImpl(NULL); // リセット // FooImplのインスタンスが作られ、 // FooImpl::someFunc()が呼ばれる Foo::someFun(); }
この書き方の利点
- 処理の中身を自由に置き換えられる
- デフォルトの処理を行わない場合、不要なインスタンスが作られない
問題点
- クラスの数が増える
- IFooを作らなくても、MockFooImplはFooImplを継承するという手もある。が、「抽象に依存」させるということでインターフェイスを作った。
- 呼び出す関数が仮想関数になる(関数呼び出しコストが増える)
- おそらく someFunc() を呼ぶコスト <<<<< 他のボトルネック なので(ry
- staticメンバ変数再び。
ちなみに、マルチスレッド環境でのSingletonの初期化問題は、シングルスレッドの間に初期化しておけ派。