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を継承するという手もある。が、「抽象に依存」させるということでインターフェイスを作った。
  • 呼び出す関数が仮想関数になる(関数呼び出しコストが増える)
  • staticメンバ変数再び。


ちなみに、マルチスレッド環境でのSingletonの初期化問題は、シングルスレッドの間に初期化しておけ派。

*1:インスタンスを削除するのはプログラム終了時だから、そもそもdelete不要という話はなし。使ったものは片付けるという、気分の問題。