タグ別アーカイブ: オブジェクト指向

Class::FileCacheable::LiteなるPerlモジュールを作った

 

Class::FileCacheableです。PODです。

何にも気にせず作ったOOPスタイルのモジュールに、後から手軽にキャッシュ機構を導入できます。

package RemoteContentGetter;
use strict;
use warnings;
use base 'Class::FileCacheable::Lite';
use LWP::Simple;

    sub new {
        my ($class, $url) = @_;
        return bless {url => $url}, $class;
    }

    sub get_url : FileCacheable {
        my $self = shift;
        return LWP::Simple::get($self->{url});
    }

    sub file_cache_expire {
        my ($self, $timestamp) = @_;
        if (time() - $timestamp > 86400) {
            return 1;
        }
    }

    sub file_cache_options {
        my $self = shift;
        return {
            namespace => 'Test',
            cache_root => 't/cache',
            default_key => $self->{url},
        };
    }

やることは、

  • Cache::FileCacheable::Liteを継承する
  • 必要ならfile_cache_expireメソッドをオーバーライドし、キャッシュの失効条件を指定する。
  • 必要ならfile_cache_optionsメソッドをオーバーライドし、オプションを明示する。
  • 対象のメソッドにFileCacheableアトリビュートを付与する。

以上です。

オプション指定などはCache::FileCacheとほぼ同じです。というか、Cache::FileCacheに依存したClass::FileCacheableってのを作ったんだけど重すぎたので、ファイル操作まで自前でやる本モジュールができたのでした。っていうか、実はこのモジュールは何年も前からひそかに使っていて、すごく気に入っていたので、今回、ちゃんとテスト書いてアップしてみた次第。継承ツリーを汚さない、MixInバージョンも作りたい。

SQL::OOPなるPerlモジュールです

SQL::OOPです。Perlです。

ORマッパーとか難しくてよくわかりません。SQL::Abstractの書式が難しすぎて覚えれません。OOPスタイルのSQLジェネレータも色々あるようですが、あまり気に入ったものがなかった。

SQL::OOPはオブジェクト指向なインターフェースでSQL::Abstractライクな結果を得るものです。簡単なことは難しく、難しいことは簡単にできます。ここ1年くらいでいくつかのプロジェクトで使ってるけど、今のところ問題なさげ。

詳しくはpodを参照ください。

例1

my $select = SQL::OOP::Select->new();
$select->set(
    $select->ARG_FIELDS => '*',
    $select->ARG_FROM   => 'table',
    $select->ARG_WHERE  => sub {
        my $where = SQL::OOP::Where->new;
        return $where->and(
            $where->cmp('=', 'a', 1),
            $where->cmp('=', 'b', 1),
        )
    },
);

こうなります。改行とインデントはフェイクです。

SELECT
    *
FROM
    table
WHERE
    "a" = ? AND "b" = ?

例2

こんな使い方しないってもの含めて、結構何でもできます。

my $select = SQL::OOP::Select->new();
$select->set(
    $select->ARG_FIELDS => SQL::OOP->new(q{"ky1", "ky2", *}),
    $select->ARG_FROM   => q("tbl1", "tbl2", "tbl3"),
    $select->ARG_WHERE  => sub {
        my $where = SQL::OOP::Where->new();
        return $where->and(
            $where->cmp('>=', 'hoge1', 'hoge1'),
            $where->cmp('=', 'hoge2', 'hoge2'),
            $where->or(
                $where->cmp('=', 'hoge3', 'hoge3'),
                $where->cmp('=', 'hoge4', 'hoge4'),
                $where->between('price', 10, 20),
                $where->is_null('vprice'),
                SQL::OOP->new('a = b'),
                'a = b',
                SQL::OOP->new('c = ? ?', ['code1', 'code2']),
                $where->between('price', 10, 20),
            ),
            $where->or(
                $where->cmp('=', 'hoge3', undef),
                $where->cmp('=', 'hoge4', undef),
            ),
        )
    },
    $select->ARG_ORDERBY => SQL::OOP::Order->abstract([['hoge1', 1], ['hoge2']]),
    $select->ARG_LIMIT  => 11315,
    $select->ARG_OFFSET => 1,
);

こうなります。

SELECT
    "ky1", "ky2", *
FROM
    "tbl1", "tbl2", "tbl3"
WHERE
    "hoge1" >= ?
    AND
    "hoge2" = ?
    AND
    (
        "hoge3" = ?
        OR
        "hoge4" = ?
        OR
        "price" BETWEEN ? AND ?
        OR
        "vprice" IS NULL
        OR
        a = b
        OR
        a = b
        OR
        c = ? ?
        OR
        "price"
        BETWEEN ? AND ?
    )
ORDER BY
    "hoge1" DESC, "hoge2"
LIMIT
    11315
OFFSET
    1

ところで、setメソッドなどの引数が変態的な書式になってますが、これはタイポをコンパイルエラーにさせるのと、入力補完するためです。

jQueryプラグインをうまいことカプセル化する設計方法

 

“There are only two hard things in Computer Science: cache invalidation and naming things”

だそうです。そういう訳で、jQueryプラグインのメソッド名が他のプラグインと衝突するのを回避するための設計方法を列挙してみた。今のところ、一番下の方法が自分的にはしっくり来ている。

[2011.02.02追記]  2011年はsub()方式がおすすめです。

jquery.myplugの基本形。基本的に$.fn.extend()は使わない方針。

/**
 * jquery.myplug基本形
 *
 * SYNOPSIS
 *
 * $('#target').myplug();
 * $('#target').myplug({param1 : val1, param2 : val2});
 */
;(function($) {

    var default_params = {
        param1 : 'a',
        param2 : 'b'
    };

    $.fn.myplug = function(params) {

        params = $.extend(default_params, params, {});

        // do something

        return this;
    }
})(jQuery);

 

複数メソッドを提供したい。

/**
 * jquery.myplug マルチメソッド(悪い例)
 *
 * SYNOPSIS
 *
 * $('#target').myplugMethod1(params);
 * $('#target').myplugMethod2(params);
 */
;(function($) {
    $.fn.myplugMethod1 = function(params) {return this;}
    $.fn.myplugMethod2 = function(params) {return this;}
})(jQuery);

上記はどこでも推奨していない悪い例。myplugというプラグインが$.fn配下にmyplugMethod1とmyplugMethod2という2つの名前を定義しているので、プラグイン間で衝突する可能性がある。

 

/**
 * jquery.myplug マルチメソッド(別案1)
 *
 * SYNOPSIS
 *
 * $('#target').myplug.method1();
 * $('#target').myplug.method2();
 *
 */
;(function($) {
    $.fn.myplug = {};
    $.fn.myplug.method1 = function(params) {}
    $.fn.myplug.method2 = function(params) {}
})(jQuery);

上記の例では、複数のメソッドを含む構造体を$.fn.myplugという名前で定義している。プラグイン間の衝突は回避できたけど、メソッド内でどうjQueryオブジェクトを取得するのか分からない。

 

/**
 * jquery.myplug マルチメソッド(別案2)
 *
 * SYNOPSIS
 *
 * $('#target').myplug().method1();
 * $('#target').myplug().method2();
 * $("#target").myplug().method1().myplug().method2();
 */
;(function($) {

    $.fn.myplug = function () {
        var jq = this;
        return {
            method1 : function(){return jq;},
            method2 : function(){return jq;}
       }
    };
})(jQuery);

thisの代わりにjqにjQueryオブジェクトを格納。インターフェースがイマイチだけど、まあまあ有り得る形。

 

/**
 * jquery.myplug 公式ドキュメント方式
 *
 * SYNOPSIS
 *
 * $('#target').myplug('method1', params);
 * $('#target').myplug('method2', params);
 */
;(function($) {

    var plugname = 'myplug';

    // 全メソッドをここに定義
    var methods = {
        init    : function(params){return this;},
        method1 : function(params){return this;},
        method2 : function(params) {return this;}
    };

    // 以下は共通
    $.fn[plugname] = function(method) {
        if ( methods[method] ) {
            return methods[ method ]
                .apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.' + plugname );
            return this;
        }
    };
})(jQuery);

jQuery公式ドキュメントでも推奨しているらしい方式。メソッド内ではthisがjQueryオブジェクトだし、開発は今までどおりで分かりやすい。ただ、メソッド名が文字列っていうのは何となく抵抗がある。

 

/**
 * jquery.myplug インナークラス方式
 *
 * SYNOPSIS
 *
 * var instance = $('#target').myplug(params);
 * instance.method1();
 * instance.method2();
 * instance.method1().method2();
 *
 * var instance2 = $('#target').data('myplug'); // can get the instance later
 */
;(function($) {

    /**
     * プラグインの名称
     */
    var plugname = 'myplug';

    /**
     * 全プラグイン共通のインナークラスコンストラクタ
     */
    var Class = function(elem, params){
        this.elem = elem;
        this.elem.data(plugname, this);
        this.params = params;
    }

    /**
     * メソッドを定義
     */
    Class.prototype.method1 = function(params){
        return this;
    };
    Class.prototype.method2 = function(params) {
        return this;
    };

    /**
     * デフォルトパラメータ
     */
    var default_params = {

    };

    /**
     * 全プラグイン共通
     */
    $.fn[plugname] = function(params){
        return new Class(this, $.extend(default_params, params, {}));
    }
})(jQuery);

プラグインひとつにつき、インナークラスを一つ定義する。元ネタ。インターフェースは、DOMに紐づいたmyplugインスタンスを引き回すのが特徴で、メソッドチェーンもできる。myplugインスタンスは$('#target').data('myplug')などとすれば後から取得できる。広く普及している、DOMを引き回すような方式とは異なるので混乱の元かも知れないけど、今のところ一番無難な方式の気がする。

ここまで来ると、プラグイン間で継承できるようなフレームワーク作ってみようとか、夢が広がる。

 

あと、同じ名前空間つながりで、イベント処理も必ず$('#target').bind('click.'+plugname, func)ってやっておいたほうが身のためです。すでに痛い目みた。