繰り返し文で複数のモジュールをまとめてロードできます。
動的なディスパッチ
コード量が少なくなることの中でも特筆すべき点として、利用するモジュールを値に応じて動的にディスパッチできることが挙げられます。
項目の多いマスタデータや、ユーザーからの入力など、プログラム外部から渡された値に応じて処理を分岐させたい場合に、コード量が大幅に少なくなるので便利です。
具体的な利用例として、ゲームでアイテムを使用する処理の実装を考えてみます。
データベースにある大量のアイテムデータと、アイテム使用時に実行される効果の処理の対応を愚直に条件分岐で実装すると大変です。しかし、次のようにアイテム名からアイテム効果モジュールのモジュール名を動的に生成し、生成したモジュール名のモジュールを動的にロードするようにすれば、少ないコード量で簡単にアイテムを使用する処理を実装できます。
起動/ロード時間の短縮
モジュールロードするタイミングを、モジュールを利用する直前にまで遅らせられるため、アプリケーションの起動やモジュールのロードにかかる時間を短くできます。Moo
、Type::Tiny、Class::Accessor::Lite
など、パフォーマンスを意識しているCPANモジュールや、巨大なWebアプリケーションでこの用途による利用をよく見かけます。
依存関係の動的な解決
依存関係を動的に解決できるので、条件に応じて依存するモジュールを変更したり、モジュールロードに失敗した場合の処理を書けます。また、モジュールが相互に依存していてもサブルーチン再定義による警告が発生しないように実装できます。
動的なモジュールロードの活用
これらの動的なモジュールロードで実現できることが、具体的にどう活用されているのかを見ていきましょう。
コード量を少なくする
コード量を少なくするために活用されている事例を見てみましょう。
Module::Findの事例──特定の名前空間に属するモジュールをすべて読み込む
Module::Findは、ある名前空間の下に属するモジュールをまとめてロードできるライブラリです。usesub
で引数の名前空間直下に属するモジュールをロードし、useall
で引数の名前空間に属するモジュールを再帰的にロードします。
Catalystのコンテナ──use忘れを防ぎ、モジュール名を短縮する
CatalystというPerl製のWAF(Web ApplicationFramework)があります。Catalyst
ではアプリケーションの起動時にすべてのモデルモジュールをロードし、モデルクラスのインスタンスをキャッシュするコンテナのようなしくみがあり、各モデルの操作はキャッシュされたインスタンスを取得して行います。これにはuse
忘れがなくなる、モジュール名が短縮された状態でメソッドを呼び出せる、一度生成したモデルクラスのインスタンスをキャッシュできるというメリットがあります。
実際にCatalyst
で作られたアプリケーションでどのようにモデルモジュールが使われるのかを見てみましょう。
このコードはCatalyst
のコントローラのコードになります。
前述したとおり、すべてのモデルモジュールは起動時にロードされるため、ここでモジュールロードをする必要はありません。
(1)では、起動時にロードしキャッシュしたモデルクラスMyApp::Model::DB::Countries
のインスタンスを取得しています。
(2)では(1)で取得したインスタンスからsearch
メソッドを呼び出しています。
Catalystのコンテナ機能と似た事例にモデルモジュール専用のモジュールローダがあり、Catalyst
とは別のWAFのプラグインなどで見受けられます。これは起動時にすべてのモデルモジュールのロードをするのではなく、モデルモジュールのインスタンスを初めて取得しようとするタイミングでモデルモジュールのロードをします。
プラガブルなアーキテクチャの実装──Tengの事例
簡単にプラグインの実装と追加ができるようになるため、プラグインを追加できるアーキテクチャの実装にもよく動的なモジュールロードが活用されています。
例としてTengのプラグイン機構を簡略化したコードでプラガブルなアーキテクチャの実装方法を紹介します。
Teng
は軽量なO/Rマッパで、メソッドを追加したりモンキーパッチ[1]を当てられるプラグイン機構が用意されています。たとえばTeng::Plugin::Count
を使うと、COUNT関数でレコード数を取得するSQLを発行するメソッドが利用できます。
プラグイン機構の実装
Teng
のようなプラグイン機構は、プラグイン名をモジュール名に変換してロードし、追加するメソッド(今回はプラグインモジュールの@EXPORT
で指定されたメソッド)をモジュール側のシンボルテーブルに追加するしくみを用意することで実現できます。
プラグインモジュールの実装
次に、プラグインモジュールを実装します。モジュールのパッケージ名はModule::Plugin::${プラグイン名}
といった風に、Module::Plugin名前空間の下に属させます。このプラグインモジュールにモジュール本体に追加したいメソッドを実装し、メソッド名は@EXPORT
に追加します。
プラグインモジュールの利用
プラグインモジュールを利用するには、「プラグイン機構の実装」で実装したadd_plugin
メソッドにプラグイン名を渡します。
モジュール本体で直接プラグインを利用すると、モジュール本体を使っているすべてのコードがプラグインによる挙動変更の影響を受けるため、モジュール本体を継承したクラスを作りそこでプラグインを利用します。
プラグインを利用したならば、プラグインモジュールによって追加されたメソッドが呼び出せるようになっています。
フレームワークの実装──Mojoliciousのルーティングの事例
動的なモジュールロードを活用すると、簡単に機能の実装ができたり、複雑なルールによる動的なディスパッチを宣言的に記述できるため、フレームワークの実装にもよく活用されます。
例として、Mojoliciousのルーティングのしくみを紹介します。
MojoliciousはPerl製のWAFで、宣言的にルーティングを記述してリクエストに応じた柔軟な処理のディスパッチができます。次のコードのように、アプリケーション起動時に呼ばれるメソッド内でリクエストに応じて呼び出されるコントローラクラスとメソッド名を記述すると、リクエストが来たときにリクエストに応じてコントローラクラスをロードし、メソッドを呼び出します。
(1)では、ルートにgetメソッドでリクエストが来た際SomeWebApp::Controller::Root
クラスのroot
メソッドを呼び出す指定をしています。
(2)では、/user
にあらゆるメソッドでリクエストが来た際SomeWebApp::Controller::User
クラスのメソッドで処理することを指定しています。$user_routes
にはその状態を保持したルーティングオブジェクトが格納されているので、この段落以降の処理ではSomeWebApp::Controller::User
クラスのどのメソッドで処理を行うかの指定をするだけで簡潔にリクエストに応じた呼び出す処理を割り当てることができます。
(3)では、/user/add/${name}
[2]にpostメソッドでリクエストが来た際に呼ばれる、SomeWebApp::Controller::User
クラスのメソッドを指定しています。
このように動的なモジュールロードを活用すると、静的に記述すると冗長になるディスパッチの記述を宣言的にかつ簡潔に記述できます。
<続きの(3)はこちら。>
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT