前回は、直接の継承を使って Phone の機能を使いましたが、今回は委譲を使ってます。
若干、回りくどくなる分だけソースコードがややこしいのですが。
// for ルイスはアリスに電話する(2) // Telephone クラスを Person と独立に機能させる #include <string.h> #include <iostream> using namespace std; class Person ; class Telephone { protected: Telephone *_telephone; Person *_person ; void (Person::*_onPhone)(const void *, int); public: // 自分の onPhone を登録 void setMyPhone(Person *person, void (Person::*onPhone)(const void *, int)) { _person = person ; _onPhone = onPhone; } // 相手先を登録 void setPhone( Telephone *telephone ) { this->_telephone = telephone ; } // データを送信 virtual void sendData( const void *data, int size ) { if ( _telephone != NULL ) { _telephone->onPhone(data,size); } } // データを受信する関数 virtual void onPhone( const void *data, int size ) { if ( _onPhone != NULL ) { (_person->*_onPhone)( data, size ); } } }; class Person { public: char _name[30]; Telephone _phone; public: // コンストラクタ Person( const char *name ) { strcpy( _name, name ); // 自分のコールバックを登録 _phone.setMyPhone( this, &Person::onPhone ); } // 相手に電話を掛ける void setPhone( Person *person ) { _phone.setPhone(person->getPhone()); } // 電話を受け取るアクセッサ Telephone *getPhone() { return &(this->_phone); } // データ受信のコールバック関数 virtual void onPhone( const void *data, int size ) { cout << "to " << _name << ": " << (char*)data << endl; } // データ送信の関数 virtual void sendPhone( const void *data, int size ) { cout << "from " << _name << ": " << (char*)data << endl; _phone.sendData( data, size ); } }; int main(void) { Person alice("alice"); Person lewis("lewis"); // アリスに電話を掛ける。 lewis.setPhone( &alice ); lewis.sendPhone( "hello i'm lewis", 0 ); return 0; }
Telephone と Person クラスの間には、継承の関係はありません。Phone has a Telephone となって、has_a の関係になりますね。ITelephone を Person クラスが継承した場合は is_a の関係です。
この Phone 機能を使う環境では、main 関数の呼び出しは変わりません(メソッド名を変えたのでそこだけです)。Person クラスの中でコールバックを登録する Person::setPhone メソッドでは、メンバ関数ポインタを使いますが、C# なんかだとデリゲートで済みます。ここは、C++ なので若干ややこしいことになっているかと。ひとつ注意したいのが、リスナーパターンを利用した場合には、Person クラスはなんらかのインターフェースを継承する必要があります(Telephone クラスからコールバックのメソッドを呼び出すため)。ですが、Person クラスの継承元を使わずに/継承させずに、Phone の機能が使えるという点で、デリゲートや関数ポインタを使った場合は異なるのです。このあたり、差異が微妙ですが、継承元というのがポイントですね。
ここで、転送するデータは音声ということで文字列しか扱っていませんが、任意のデータを送ることができます。分かりますね、
アリスは物理転送機を持っている | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2025
のところで話した、シリアライズの機能を使ってプラダのバッグを送ることができるのです。
ってな訳で、この話は次回に。