デザインパターン

2010年3月 6日 (土)

Factory Method(ファクトリーメソッド)でNULLチェックをなくした

クラスで使用するオブジェクトが初期化されていないときは、何もしないでね。みたいな場面は多々としてあると思います。

コードで書くと、以下のような感じです。

void CNanika::proc(void) {
    // オブジェクトのNULLチェック     if(m_Ctrl == NULL ) {
        return;
    }
    // メイン         ・     string name = m_Ctrl->getName();         ・         ・ }

一つや二つの関数だったらいいですけれども、たくさんあったら面倒くさいですね。なによりも、抜けとかあったら、とたんにアクセスエラーですから。

目標としましては、オブジェクトのNULLチェック部分を追い出してみたいと思います。さて、どのように追い出しましょうか。まず、処理を行う/行わないクラスに分けますか。

とりあえず、処理を行うクラスをCNanikaクラスで、行わないクラスをCNanika_NULLクラスとしましょう。

CNanikaクラスとCNanika_NULLクラスの関数定義は一致していなければならないので、関数定義を集めたCNanika_Interfaceクラスを基底クラスとして、CNanika, CNanika_NULLクラスに継承させます。

とりあえず、用意する関数は、上記コードにも書いたproc()だけとします。

コードにします。

class CNanika_Interface {
public:
    virtaul void proc( void ) = 0;
protected:
    virtual ~CNanika_Interface() {}
};

class CNanika_NULL : public CNanika_Interface {
public:
    virtaul void proc( void ) { return; }
protected:
    CNanika_NULL() {}
    virtual ~CNanika_NULL() {}
};

class CNanika : public CNanika_Interface {
public:
    virtaul void proc( void ) {
        // メイン             ・         string name = m_Ctrl->getName();             ・             ・     } protected:
    CNanika() {}
    CNanika( CCtrl* ctr ) : m_Ctrl( ctr ) {}
    virtual ~CNanika() {}
protected:
    CCtrl* m_Ctrl;
};

あとは、CCtrlがNULLかどうかでCNanika_NULLもしくは、CNanikaクラスオブジェクトを生成して、CNanika_Interfaceを経由して提供してあげれば、良さそうです。あと、CNanika_NULLとCNanikaクラスは勝手に生成されてはこまるので、コンストラクタをprotectedに配置しています。

あとは、生成部だけですが、生成部には、factory methodパターンを使います。

factory methodをwikiで調べてみると、『オブジェクトの生成をサブクラスに委ねることによって、プログラム内で使用されるインスタンスの型の制約を緩める』ということみたいですね。

これは、CNanika, CNanika_NULLクラスを生成するクラスを別に用意するということになります。とりあえず、そのクラスは、CNanika_Factoryとしましょう。あとは、CCtrlのありなしでCNanikaかCNanika_NULLを生成してあげればいいのです。

では、コードです。

class CNanika_Factory {
public:
    // 生成     static CNanika_Interface* create( CCtrl* ctr ) {
        if( ctr != NULL ) {
            return new CNanika( ctr );
        }
        else {
            return new CNanika_NULL;
        }
    }
    // 破棄     static void release( CNanika_Interface* obj ) {
        if( obj != NULL ) {
            delete obj;
        }
    }
};

生成部では、ctrの中身をチェックして、NULLじゃなければ、CNanikaを生成して返します。NULLの場合は、CNanika_NULLとなります。

あと、生成を用意したので、破棄も用意してあげます。

このままでは、CNanika_Factoryは、CNanikaとCNanika_NULLを生成できない(CNanikaとCNanika_NULLのコンストラクタがprotected属性であるため)ので、CNanika, CNanika_NULLにfriend classを追加して、完成です。

以下、完成コードになります。

class CNanika_Factory;

class CNanika_Interface {
    friend class CNanika_Factory;
public:
    virtaul void proc( void ) = 0;
protected:
    virtual ~CNanika_Interface() {}
};

class CNanika_NULL : public CNanika_Interface {
    friend class CNanika_Factory;
public:
    virtaul void proc( void ) { return; }
protected:
    CNanika_NULL() {}
    virtual ~CNanika_NULL() {}
};

class CNanika : public CNanika_Interface {
    friend class CNanika_Factory;
public:
    virtaul void proc( void ) {
        // メイン
            ・
        string name = m_Ctrl->getName();
            ・
            ・
    }
protected:
    CNanika() {}
    CNanika( CCtrl* ctr ) : m_Ctrl( ctr ) {}
    virtual ~CNanika() {}
protected:
    CCtrl* m_Ctrl;
};

class CNanika_Factory {
public:
    // 生成
    static CNanika_Interface* create( CCtrl* ctr ) {
        if( ctr != NULL ) {
            return new CNanika( ctr );
        }
        else {
            return new CNanika_NULL;
        }
    }
    // 破棄
    static void release( CNanika_Interface* obj ) {
        if( obj != NULL ) {
            delete obj;
        }
    }
};

今回は、一関数のみのNULLチェックを追い出しただけですので、コード増えすぎ、煩雑になりすぎって感じですが、たくさんの関数のNULLチェックを追い出すときは、結構有用ではないかと思っています。

| | コメント (0) | トラックバック (0)

2010年1月29日 (金)

知らずに使っていたよ、Template Methode(テンプレートメソッド)パターン

さてさて、クラス設計をしていると、スーパークラスにこういうメソッドがありますよ、というものを用意して、そのメソッドの実装は、サブクラスで行うなんてことは、結構やりますよね。

実は、これ、デザインパターンのTemplate Methodパターンってやつだったんですね。

では、コード例です。

class Fruit {
public:
    Fruit() {}
    virtual ~Fruit() {}
    virtual string getName( void ) const = 0;
    virtual string getColor( void ) const = 0;
};

スーパークラスでは、メソッドの定義だけしてあげます。余談ですが、スーパークラスのデストラクタには、『virtual』を付けておくのを忘れずに。サブクラスのデストラクタが呼び出されなくなりますからね。

サブクラスでは、スーパークラスのメソッドを実装します。

まずは、リンゴクラス。

class Apple : puclic Fruit {
public:
    virtual ~Apple() {}
    virtual string getName( void ) const { return "Apple"; }
    virtual string getColor( void ) const { return "Red"; }
};

そして、オレンジクラス。

class Orange : public Fruit {
public:
    virtual ~Orange() {}
    virtual string getName( void ) const { return "Orange"; }
    virtual string getColor( void ) const { return "Orange"; }
};

使用するときには、リンゴ、オレンジのオブジェクトを生成して、果物クラスのポインタへ入れてあげます。コードにすると、こんな感じです。

int main(int argc, char *argv[]) {
    Fruit* f1 = new Apple();
    Fruit* f2 = new Orange();
    cout << f1->getName() << ":" << f1->getColor() << endl;
    cout << f2->getName() << ":" << f2->getColor() << endl;
    delete f1;
    delete f2;
    return 0;
}

ちなみに、実行すると、下記のようになります。

Apple:Red
Orange:Orange

次回、『new Apple()』『new Orange』のあたりをちょこっといじくります。

| | コメント (0) | トラックバック (0)

2009年8月26日 (水)

疑似乱数を調べていたら、Strategy(ストラテジ)パターンがでてきた

最近、読んだ本のなかに、疑似乱数生成法に『XorShift法』というものが紹介されていました。この『XorShift法』は、Xorとシフト演算のみで疑似乱数を生成するため、処理が軽いという特徴があります。また、周期やランダム性をとっても、十分実用レベルだそうです。疑似乱数といえば、質と速度には定評のある『メルセンヌツイスター法』が有名ですが、これと比較して、処理速度がどのくらいなのか調べてみました。あと、おまけで、標準関数のrand()も。

Opteron 1.8GHz + Visual Studio 2008上で1000万回生成の処理時間(100回計測して、その平均をとっています)を計測した結果、以下のようになりました。

方法処理時間(ms)
XorShift98.57
メルセンヌツイスター203.47
標準関数rand()483.59

ということで、XorShift法が一番速いという結果になりました。ゲームプログラミングなど速度が重視されるようなものに、最適ではないでしょうか。

さて、処理時間を調べるため、プログラムをごりごり書いていたら、デザインパターンのStrategyパターンが出てきましたので、紹介いたします。Strategyパターンは、アルゴリズムの交換を可能にするものです。アルゴリズムを交換する、いったいどういうことでしょうか。

それでは、まず、計測に使ったプログラムの一部を示します。

    random_xor rand_xor;    /* XorShift */
    random_std rand_std;    /* 標準 */
    random_mt  rand_mt;      /* メルセンヌツイスター */
    random* rand_array[] = { &rand_xor, &rand_std, &rand_mt };
    for( int i=0; i<3; i++ ) {
        random* rand = rand_array[i];
        /* ----- 計測 ----- */
        for( int n=0; n<100; n++ ) {
            for( int nn=0; nn<10000000; nn++ ) {
                rand->generate();
            }
        }
    }

random_xor、random_std、random_mtはそれぞれ、XorShift法疑似乱数生成クラス、標準関数疑似乱数生成クラス、メルセンヌツイスター法疑似乱数生成クラスです。6行目のところでクラスを交換して、各方法の疑似乱数を生成しています。

つまり、このプログラムでは、疑似乱数生成アルゴリズムの交換を行っています。このようにアルゴリズムの交換を可能にする仕掛けをStrategyパターンと呼びます。

では、Strategyパターンを取り入れた、疑似乱数クラスはどうなっているのでしょうか。下記にヘッダー部を示します。

/* ----- 基底クラス ----- */
class random {
public:
    random() {}
    virtual ~random() {}
public:
    virtual int generate( void ) = 0;
};  /* class */

/* ----- XorShift法クラス ----- */
class random_xor : public random {
public:
    random_xor( void );
    virtual ~random_xor();
public:
    virtual int generate( void );
};  /* class */

/* ----- 標準関数クラス ----- */
class random_std : public random {
public:
    random_std();
    virtual ~random_std();
public:
    virtual int generate( void );
};  /* class */

/* ----- メルセンヌツイスター法クラス ----- */
class random_mt : public random {
public:
    random_mt();
    virtual ~random_mt();
public:
    virtual int generate( void );
};

まず、基底クラスにて入れ替えを可能にするメンバ関数(上記のソースでは、generate()関数が該当します)を定義します。あとは、基底クラスを派生してクラスを生成し、メンバ関数を実装すれば完了です。なんだか、ポリモーフィズムやらで知らず知らずの内に使っているようなパターンですね。

今回はここまで!

| | コメント (0) | トラックバック (0)

2009年7月27日 (月)

singleton(シングルトン)パターン 其の6

では、singletonのテンプレートクラスを使って、以前作成したログクラスを作成します。

今回は、singletonのテンプレートクラスを派生するだけですので、さくっと実装してしまいます。

まず、ヘッダー部です。

class singleton_log : public singleton<singleton_log> {
    SINGLETON( singleton_log )
    /* コンストラクタ */
    singleton_log();
    /* デストラクタ */
    virtual ~singleton_log() {}
public:
    /* 生成 */
    static singleton_log* create( void );
    /* 出力 */
    void out( const char* _log );
};

クラス宣言部に、テンプレートクラスの派生と、テンプレートクラスから派生クラスへアクセスするためのキーワードを記述します。

続いて、ソース部です。

/* ログファイル名 */
static const char LOG_FILENAME = "log.txt";

/* ----- コンストラクタ ----- */
singleton_log::singleton_log() {
    FILE* fp = ::fopen( LOG_FILENAME, "w" );
    ::fprintf( fp, "ログ開始\r\n" );
    ::fclose( fp );
}

/* ----- 生成 ----- */
singleton_log* singleton_log::create( void ) {
    return singleton<singleton_log>::create();
}

/* ----- 出力 ----- */
void singleton_log::out( const char* _log ) {
    FILE* fp = ::fopen( LOG_FILENAME, "a+" );
    ::fprintf( fp, _log );
    ::fclose( fp );
}

コンストラクタと出力関数は変わっていません。生成関数はオブジェクト生成関数の呼び出しだけです。 以前作成したログクラスと比較して、結構すっきりしたかなと思います。

使うときは、

singleton_log::create();

生成関数を呼び出して、

singleton_log* pObj = singleton<singleton_log>::get();

とすれば、singletonのクラスオブジェクトが取得できます。 テンプレートに抵抗があるならば、

#define GET_SINGLETON( T ) singleton<T>::get()

こんな感じのを定義しておけば、

singleton_log* pObj = GET_SINGLETON( singleton_log );

このように取得できます(あんまり変わらなかったかな・・・)。

以上で、singletonパターンの勉強は完了です。

次回は、なにをやろうかな。

| | コメント (0) | トラックバック (0)

2009年7月24日 (金)

singleton(シングルトン)パターン 其の5

では、singletonのテンプレートクラス実装です。

まず、コンストラクタ、代入演算子、取得関数や解放関数は基本のsingletonクラスと同じで大丈夫ですね。生成関数は、派生したクラスで記述するようにしますので、public属性からprotected属性に変更します。

そうすると、以下のようになるでしょうか。

template<class T> class singleton {
protected:
    /* オブジェクト */
    static T* Object;
protected:
    /* コンストラクタ */
    singleton() {}
    /* コピーコンストラクタ */
    singleton( const singleton& _rhs ) {}
    /* 代入演算子 */
    singleton& operator = ( const singleton& _r ) {}
    /* デストラクタ */
    ~singleton() {}
protected:
    /* 生成 */
    static T* create( void ) {
        if( Object != NULL ) {
            return Object;
        }
        Object = new T();
        return Object;
    }
public:
    /* 取得 */
    static T* get( void ) {
        return Object;
    }
    /* 解放 */
    static void release( void ) {
        if( Object != NULL ) {
            delete Object;
            Object = NULL;
        }
    }
};

/* ----- オブジェクト ----- */
template<typename T>T* singleton<T>::Object = NULL;

/* ----- singletonクラスから派生クラスへのアクセス用 ----- */
#define SINGLETON( T ) protected:friend class singleton<T>;

テンプレートクラスができましたが、基本のsingletonクラスとそんなに変わりありませんでしたね。主な変化点としては、外部からのアクセスを防ぐ変数や関数をprivate属性からprotected属性に変更しています。これは、派生したクラスの生成関数からアクセスする必要があるためです。また、一番最後に、singletonテンプレートクラスから派生したクラスへのアクセスするためのキーワードを定義して完了です。

次回は、このテンプレートクラスを使用して、以前に作ったログクラスを作ってみます。

| | コメント (0) | トラックバック (0)

2009年7月21日 (火)

singleton(シングルトン)パターン 其の4

では、singletonクラスのテンプレート化を考えてみたいと思います。

まず、テンプレート化ってなんでそんなことをするのか?

singletonパターンを持ったクラスを作りたいときに、一から全部書いてもいいのですが、singletonパターンでは、ある程度、定型的に書けることが、前の記事の内容で分かっています。

定型的に書けるところはなるべく書きたくないですよね。また、一から書いてバグを書き込んじゃっても嫌ですし。

そんな理由でテンプレート化を行います。

さて、テンプレート化をしようかと思ったのですが、単純にテンプレート化するだけでは、駄目みたいですね。基本のsingletonクラスと、前回のログクラスを見てみますと、コンストラクタの内容とか変わってるし、out()関数 なんかが、追加されているしで。

どうしましょうか。

基本のクラスの機能を書き換えたり、新機能を持たせたりするのであれば、派生してあげればいいので、singletonのテンプレートクラスを派生して、singletonパターンを持つクラスを作ってあげようという線でいきましょう。

singletonテンプレートクラスの設計方針としましては、以下のようにしたいと思います。

  • singletonパターンを持ったクラスは、singletonテンプレートクラスを派生させます。
  • get()とrelease()関数は特にいじる必要はないと思われるので、そのままとします。
  • create()関数は生成するときに、引数指定で呼び出したりということが考えられますので、create()関数は派生クラスに用意してもらいます。ただし、オブジェクトを生成する部分はsingletonテンプレートクラスで行います。

次回、これらを踏まえて、実装してみたいと思います。

| | コメント (0) | トラックバック (0)

2009年7月19日 (日)

singleton(シングルトン)パターン 其の3

では、singletonパターンを使ったクラスの構築を考えてみたいと思います。

singletonはクラスのインスタンスが一つであるということが保証されるということですから、いろんなところからアクセスされるけど、処理等を単一にしたいという場合とかに有効かと思われます。

いろいろ調べてみましたが、インスタンスの生成を抑えたい場合にも有効とかかれていましたが、これは、設計段階とかでは考えない方が良いかと思います。インスタンスの生成が少なくなるように設計するのが筋かなと思います。

では、いろんなところからアクセスされるけど、処理を単一なクラスの具体例を一つあげますと、ログクラスなんていかがでしょうか。

ログの出力はいろいろなところから呼び出されるけど、出力先のログファイルは一つという機能をもたせたクラスにしてみます。

では、実装です。まず、ヘッダー部です。

class singleton_log {
    private:
        /* オブジェクト */
        static singleton_log* Object;
    private:
        /* コンストラクタ */
        singleton_log();
        /* コピーコンストラクタ */
        singleton_log( const singleton_log& _r ) {}
        /* 代入演算子 */
        singleton_log& operator = ( const singleton& _r ) {}
        /* デストラクタ */
        virtual ~singleton() {}
    public:
        /* 生成 */
        static singleton_log* create( void );
        /* 破棄 */
        static void release( void );
        /* 取得 */
        static singleton_log* get( void ) const;
        /* 出力 */
        void out( const char* _log );
};

シングルトンの基本に、ちょっと手を加えました。ヘッダー部には、出力関数を加えました。

では、ソース部です。

/* ログファイル名 */
static const char LOG_FILENAME = "log.txt";
/* オブジェクト */
singleton_log* singleton_log::object = NULL;

/* ----- コンストラクタ ----- */
singleton_log::singleton_log() {
    FILE* fp = ::fopen( LOG_FILENAME, "w" );
    ::fprintf( fp, "ログ開始\r\n" );
    ::fclose( fp );
}
/* ----- 生成 ----- */
singleton_log* singleton_log::create( void ) {
    if( object == NULL ) {
        object = new singleton_log;
    }
    return object;
}
/* ----- 破棄 ----- */
void singleton_log::release( void ) {
    if( object != NULL ) {
        delete object;
        object = NULL;
    }
}
/* ----- 取得 ----- */
singleton_log* singleton_log::get( void ) const {
    return object;
}
/* ----- 出力 ----- */
void singleton_log::out( const char* _log ) {
    FILE* fp = ::fopen( LOG_FILENAME, "a+" );
    ::fprintf( fp, _log );
    ::fclose( fp );
}

生成、破棄、取得は何も変わっていません。コンストラクタにログの開始メッセージを書き込む処理と、ログを出力するための出力関数を追加しました。

このクラスを使用するときは、

singleton_log::create();

を呼び出して、クラスを生成すれば、どこからでも下記のようにしてログ出力関数を呼び出すことができます。

singleton_log* sl = singleton_log::get();
sl->out( "ログを出力するよ\r\n" );

使い終わったら、破棄関数を呼んでください。

次回は、singletonをテンプレート化してみます。

| | コメント (0) | トラックバック (0)

2009年7月16日 (木)

singleton(シングルトン)パターン 其の2

では、昨日調べたsingletonパターンを実際のコードに起こしてみますか。

まず、クラス生成に制限を加えるということですので、コンストラクタ等に外部からアクセスできないように、privateにしてしまいます。

それから、生成用の関数とクラス実体を返す関数を用意すればいいわけです。
昨日、書き忘れましたが、生成を作ったら、破棄関数も用意しておきましょう。

自動的に破棄するような実装もできますが、ここは、明示的にいきましょう。

まず、ヘッダー部分は以下のようになるでしょうか。

class singleton {
private:
    /* 実体 */
    static singleton* object;
private:
    /* コンストラクタ */
    singleton() {}
    /* コピーコンストラクタ */
    singleton( const singleton& _r ) {}
    /* 代入演算子 */
    singleton& operator = ( const singleton& _r ) {}
    /* デストラクタ */
    virtual ~singleton() {}
public:
    /* 生成 */
    singleton* create( void );
    /* 破棄 */
    void release( void );
    /* 取得 */
    singleton* get( void ) const;
};

続いて、ソースはこんな感じ。

singleton* singleton::object = NULL;

/* ----- 生成 ----- */
singleton* singleton::create( void ) {
    if( object == NULL ) {
        object = new singleton;
    }
    return object;
}

/* ----- 破棄 ----- */
void singleton::release( void ) {
    if( object != NULL ) {
        delete object;
        object = NULL;
    }
}

/* ----- 取得 ----- */
singleton* singleton::get( void ) const {
    return object;
}

生成部分では、初回で呼ばれたときに、クラス実体を生成します。それ以外では何もしません。

破棄は、クラス実体があるときに破棄するだけです。

取得は、クラス実体(生成されていないときはNULLになります)を返します。

これにて、実装完了!

次回は、singletonの具体例をあげてみます。

| | コメント (0) | トラックバック (0)

2009年7月15日 (水)

singleton(シングルトン)パターン 其の1

では、GOFデザインパターンから、singletonパターンについてです。
singletonパターンとは、『クラスのインスタンスが一つであることを保証する』ということです。

これを実現するには、クラスの生成に制限を加えてやることで実現できます。
生成に制限を加えるには、まず、勝手に生成されないような仕組みをクラスに実装してあげる必要があります。

では、クラスの生成とは、どのようなタイミングで行われるのでしょうか。調べてみると、以下の関数がコールされると生成されるみたいです。

  • コンストラクタ
  • コピーコンストラクタ
  • 代入演算子

まず、これらの関数を外部からアクセスされないよう制限を加えてあげます。

でも、このままでは、クラスの生成は全くできないということになってしまいます。なので、外部 からもアクセスできる生成関数を用意してあげます。この生成関数に『クラスのインスタンスが一つである』 という仕組みを盛り込んであげればいいわけです。

あともう一つ、インスタンスを取得する関数も必要ですね。生成関数で代用することもできますが、 あっちこっちで生成関数が呼ばれてたら、混乱すること間違いなしですからね。

次回、これらを踏まえて実装してみます。

| | コメント (0) | トラックバック (0)