― Web Technology and Life ―

PerlのCPANモジュールObject::ContainerにみるSingletonパターン

2011-09-17
『PerlのCPANモジュールに学ぶオブジェクトデザインパターン』第二弾としてObject::ContainerからSingletonパターンを読み取ります。

問題:毎回同じインスタンスを生成している

前回の『PerlのCPANモジュールQudoにみるTemplateMethodパターン』で紹介したQudoのようなモジュールのインスタンスを生成するときには、以下のような長いコンストラクションコードを書いてジョブの登録とかを行います。

my $client = Qudo->new(
    driver_class => 'Skinny', # DBIx::Skinny
    databases => [
        +{
            dsn      => 'dbi:SQLite:/tmp/qudo.db',
            username => '',
            password => '',
        }
    ],
);

# enqueue job
$client->enqueue("Your::Worker::Mail", { 
    arg => $user->email, 
    uniqkey => $user->login_id,
});

いろんなクラスでいろいろなジョブを登録していこうと思ったそれぞれのクラスファイルでだらだらとnewしなくてはいけないのは、実に面倒ですし、結局同じインスタンスを生成しているのなら、その生成に使っているCPUコストも、生成した分のメモリ量も無駄です。

Singletonパターンで解決

先ほどの問題は、端的に言えば、何回も同じこと(同一インスタンス生成)をしているが問題でした。

Singletonパターンとは

このようなときに効果的な設計がSingleton(シングルトン)パターンです。Singletonパターンは、生成されるインスタンスを唯一のもであるということ保証するというデザインパターンです。

それでは、早速どのような方法でObject::ContainerがSingletonパターンを実装しているか確認します。

CPANモジュールObject::ContainerにみるSingletonパターン

Object::Containerはシングルトンパターンを実装できるインスタンス(オブジェクト)管理モジュールです。以下のようなコードがObject::Containerのインスタンスが一つにであることを保証してくれます。

my %INSTANCES;
sub instance {
    my $class = shift;
    return $INSTANCES{$class} ||= $class->new;
}

重要なポイントは、instanceメソッドの外に%INSTANCES;がいることです。instanceメソッドからObject::Containerのインスタンスを呼び出すことで、はじめて呼び出すときはObject::Containerのインスタンスを生成して%INSTANCESにインスタンスをため、次どこから呼び出しても、新しくインスタンスが生成されることなく、%INSTANCESにはいったインスタンスを返すことになるのです。%INSTANCESのようにハッシュになっているところは、Object::Containerのクラスでも、独自に実装できるサブクラスのMyApp::Containerのクラスでも入るようになっているから(?)だと思います。

Object::Containerの使用によるSingletonパターン実装

ちょっとややっこしいのですが、上記のObject::Containerの実装を参考にしてQudoのラッパーを以下のように書けば先ほどのQudoのインスタンスもシングルトン管理ができますが、

package MyApp::Qudo;
use strict;
use warnings;
use Qudo;

my $qudo = Qudo->new(
    driver_class => 'Skinny', # DBIx::Skinny
    databases => [
        +{
            dsn      => 'dbi:SQLite:/tmp/qudo.db',
            username => '',
            password => '',
        }
    ],
);

sub instance { $qudo }

以下のように、Object::Containerを使うとシングルトン管理が楽に実装できます。シングルトンパターンで生成されているObject::Containerのインスタンスにインスタンスを登録するからインスタンスは唯一ひとつになるということです。

#サブクラスでインスタンスの登録
package MyApp::Container;
use strict;
use warnings;
use Object::Container '-base';

use Qudo;
register qudo => sub { 
    Qudo->new(
        driver_class => 'Skinny', # DBIx::Skinny
        databases => [
            +{
                dsn      => 'dbi:SQLite:/tmp/qudo.db',
                username => '',
                password => '',
            }
        ],
    );
};

#登録したQudoインスタンスを使用する
use MyApp::Container qw/obj/;

obj('qudo')->enqueue("Your::Worker::Mail", { 
    arg => $user->email, 
    uniqkey => $user->login_id,
});

以下のように、printでインスタンスの実体を比較すると、MyContainer->instanceで取り出したqudoオブジェクトとobj('qudo')が同じ所を指しているのがわかります。

#インスタンスの実体を表示するコード
use MyApp::Container qw/obj/;

my $container = MyApp::Container->instance;

print $container->get('qudo')."\n";
print obj('qudo')."\n";
#出力内容
Qudo=HASH(0x1b6f480)
Qudo=HASH(0x1b6f480)

まとめ~使いどころ、メリット/デメリット~

メリット

  • 都度インスタンス生成するCPUコストの削減
  • 無駄なインスタンス生成によるメモリの削減
  • 同じコードの繰り返しによるコードのメンテナンス性の低下を防ぐ
  • 同じコードの繰り返し書かないことによる開発スピードの向上

どこからインスタンスが生成されてもキャッシュされているので、場所を選ばずに永続的なインスタンス生成とその利用が担保される強力な手法といえます。

デメリット

  • どこからでもアクセスできるグーバル変数化によるスコープ無限拡大
  • インスタンスの中身が変化するタイプのインスタンスには使用できない

どこからでもアクセスできるグーバル変数化によるスコープ無限拡大というのは、どこからでも使えてしまうために本来使用するべきではないところからもアクセスできてしまうし、実際、どこで使っているかよくわからなくなるということです。次の、インスタンスの中身が変化するタイプのインスタンスには使用できない、というのは、DateTimeとか、足し算とか引き算しているうちのインスタンスの中身が変わってしまうタイプのインスタンスには使えないという、デメリットというか注意点です。

最後に

オブジェクトデザインパターンにあてはまりやすいモジュールを探すのはなかなか大変ですね。。。同じモジュールは二回登場させたくないなーと思っている(できるだけいろんなモジュールのコード読みたい)のですが、難しそうです。。。また、おかしいところのご指摘は随時募集中ですので、よろしくお願いします。この連休中に、あと、二つくらいまとめたいなー。

Perl update_at : 2011-09-19T16:45:29
hirobanex.netの更新情報の取得
 RSSリーダーで購読する   
blog comments powered by Disqus