ホーム < ゲームつくろー! < Ogg Vorbis入門編

その7 Ogg Vorbisクラスのリファクタリング


 前章まででOgg Vorbisファイル及びメモリに展開したOggファイルのストリーム再生をするクラスを作成しました。おさらいすると、OggファイルをデコードするOggDecoderクラス(親:PCMDecoderクラス)をPCMPlayerクラスに渡すと音が鳴るのでした。メモリにあるOggファイルはOggDecoderInMemoryクラスで扱います。

 さて、機能としては動いているこれらのクラスですが、掲示板に「ファイルとメモリという読み込み元が違うだけでデコーダクラスを2つ作るのは良くないのでは」というご意見を頂きました。これはなるほどなんです。前章にも記述があるのですが、OggDecoderクラスとOggDecoderInMemoryクラスの違いは「読込先」でして、最終的にOggVorbis_File構造体を作るという共通目的を持っています。そこから先のデコード作業は両者で一緒です。

 デザインパターンの原則に「不変な部分と変化する部分を分けて、変化する部分は外に出せ」というのがあります。クラスの継承を使うとついついこの変化する部分を派生クラスで表現したくなるのですが、これはデザインパターンでは好まれません。前章までのクラスはこの「好まれない」設計になっているわけです。

 そこで本章では前章まで作ったクラスをリファクタリングして整理する事にします。ただ、細かい実装部分は瑣末的過ぎてあまりにつまらないので、要点を抑えるだけにとどめます。



@ 不変な部分と変化する部分

 ここまで作ってきたクラスを使ったOggファイル再生の流れを見てみます。

<OggDecoderクラス>
1. Oggファイルを読み込んでOggVorbis_File構造体を作る
2. OggVorbis_File構造体からOggファイルの情報を取得する
3. OggVorbis_File構造体を通して指定の長さ分デコードする

<OggPlayerクラス>
4. デコードされた部分を再生する

またOggDecoderInMemoryクラスを使った場合は、

<OggDecoderInMemoryクラス>
1. Oggファイルをメモリに展開して読み込みOggVorbis_File構造体を作る
2. OggVorbis_File構造体からOggファイルの情報を取得する
3. OggVorbis_File構造体を通して指定の長さ分デコードする

です。これを見るとOggDecoderクラスとOggDecoderInMemoryクラスは赤文字部分「だけ」違います。先の実装ではこれをクラスの継承で解決しました。しかし、冒頭でも述べましたが、デザインパターンでは変化する部分を外に抜き出す事を原則にしています(継承は変更に頑健ではないため)。上で変化する部分は赤文字の部分、不変な部分はその他です。ここから、「リソースから情報を読み取ってOggVorbis_File構造体を作る」という部分がクラスとして外に抜き出される候補になります。



A OggVorbisResourceクラス

 @で抽出した変化する部分をOggVorbisResourceクラスに吸収してもらうことにします。このクラスはOggVorbis_File構造体を保持し、その管理を行います。またその6であれこれ悩んだ「複製」についても管理してもらうことにします。

 OggVorbisResourceクラスは抽象クラスで対象となる具体的なリソースを知らないため、非常にシンプルになります。情報取得用の各種インターフェイスを提供するだけです:

OggVorvisResourceクラス宣言部(OggVorbisResource.h)
#ifndef IKD_DIX_OGGVORBISRESOURCE_H
#define IKD_DIX_OGGVORBISRESOURCE_H

#include "vorbis/vorbisfile.h"
#include "DixSmartPtr.h"
#include "memory.h"

namespace Dix {
   class OggVorbisResource {
   public:
      //! コンストラクタ
      OggVorbisResource() : isReady_( false ){
         memset( &oggVorbisFile_, 0, sizeof( OggVorbis_File ) );
      }

      //! デストラクタ
      virtual ~OggVorbisResource(){
      }

      //! クリア
      virtual void clear() {
         memset( &oggVorbisFile_, 0, sizeof( OggVorbis_File ) );
         isReady_ = false;
      }

      //! OggVorbis_File構造体を取得
      virtual OggVorbis_File& getOggVorbisFile() {
         return oggVorbisFile_;
      }

      //! 安全なクローンを作成
      virtual sp< OggVorbisResource > createClone() = 0;

      //! 準備できた?
      bool isReady() {
         return isReady_;
      }

   protected:
      OggVorbis_File oggVorbisFile_;  // OggVorbis_File構造体
      bool      isReady_;             // 準備できた?
   };
}

#endif

 短いソースです。仕事は2つ、OggVorbis_File構造体を渡すことと自身のクローンを生成する事です。実装はこのクラスの派生クラスで行います。最も基本であるOggファイルを読み込むクラス(OggVorbisFileクラス)やメモリに展開したOggファイルを読み込むクラス(OggVorbisMemoryクラス)をここから派生させるわけです。

 それが出来れば、今度はこのクラスを扱う側を変更します。



C PCMDecorderクラス及びOggDecorderクラスの変更

 OggVorbisResoruceクラスがあると、リソースを扱う側になったPCMDecoderクラスやOggDecoderクラスに内在していた「ファイル」という概念が無くなります。PCMクラスの中にファイル名がありましたが、これがいらなくなるわけです。

 PCMDecoder::setSoundメソッドにはファイル名を指定するようになっていました。これをOggVorbisResourceに変更…というわけにはいきません。PCMDecoderクラスの仕事はデコードだけにして、扱っているリソースからどうPCM音声を取り出すかは派生クラスに実装してもらうことにします。よってPCMDecoder::setSoundメソッドは削除します。

 その他に、OggDecoderクラスにsetResourceメソッドを追加(中身はsetSoundメソッドでやった事と一緒)したり、細かく変更点があるのですが…さすがに瑣末過ぎて全部書ききれません(^-^;。いったんは書きかけましたが、あまりにつまらないので消しました。要は、OggDecoderの中で行っていた読み込みとデコードの作業が分離されて、リソースの読み込みはOggVorbisResourceクラスで、デコードはOggDecoderで行えるようになったというのがこの章の大きな結論です。



D クローンの扱い

 前章までであれこれ考えたクローンも、読み込み部分とデコード部分を分けた事で扱いが少し変わり、かなり扱いやすくなりました。前章まではOggDecoderクラス及びOggDecoderInMemoryクラスそれぞれでかなり面倒なクローン化をしたわけですが、今回のリファクタリングでOggDecoderクラスは扱っているOggVorvisResourceクラスの持つcreateCloneメソッドを呼ぶだけで良くなりました。

 この副次効果として、OggDecoderクラス自体のクローンが簡単にできるようになります:

OggDecoderオブジェクトの複製(OggDecoder.cpp)
//! 安全なクローンを生成
sp< PCMDecoder > OggDecoder::createClone() {
   sp< OggDecoder > spObj( new OggDecoder );
   if ( oggVorbisResource_->isReady() == false ) {
      return spObj; // 空を返す
   }

   // クローンを作成
   // OggDecoder::setResourceメソッドの内部で
   // クローンを作成して保持しています
   spObj->setResource( oggVorbisResource_ );

   return spObj;
}

 たったこれだけの事なんですが効果覿面でして、これによりPCMPlayerは本体ではなくてクローンを保持することが可能になります。これにより、沢山のプレイヤーを用意して「1つのデコーダを使いまわす」事ができるようになりました:

Dix::sp< Dix::OggDecoder > spOggDecoder( new Dix::OggDecoder( spOggResource ) );
Dix::PCMPlayer player[ 10 ];

for ( int i = 0 ; i < 10; ++i ) {
   player[ i ].setDevice( cpDS8 );
   player[ i ].setDecoder( spOggDecoder ); // <- 同じリソースを渡せる!
}

 変化する部分を外に出すというデザインパターンの原則が、色々な部分の整合性をより保ちやすくしてくれました。



 このリファクタリングにより、OggVorbisの扱いは実質Oggをどうやって読み込むか、言い換えればOggVorbisResourceクラスをどう実装するかという部分だけになりました。突き詰めればもっと改良はできるかもしれませんが、それは追々ですね。

 リファクタリング後のクラス群はサンプルプログラムにて公開します。前のクラスとはインターフェイスが変わってしまったので使用の際はご注意下さい。



D 謝辞

 クラスの改良案をご提示下さいました藍川翡翠様に感謝申し上げます。