Functors
Functors APIの目的は、手続き型プログラム要素をオブジェクトとしてモデル化することです。たとえば、何かの条件に関してVector内またはArrayList内のオブジェクトを評価し、その結果に基づいてアクションをとるようなコードを書いたことはないですか? あるいは、if-elseif-elseif-else文やswitch-case文を書いた経験は? Functorsを使用すると、if-elseやswitch-caseのような条件とこれらの条件の中の式を、独立したオブジェクトとしてモデル化することができます。では、なぜそうする必要があるのでしょうか。この手法には次のような利点があります。
- モジュール型設計になるため、機能の拡張や修正が容易になります。StrategyパターンとCommandパターンを一緒に使って、操作の中のアルゴリズムを条件によって切り替えることができます。
- 単体テストケースを書くことが容易になります。
- リフレクションを使って、条件に応じた操作を動的にロードすることができます。
技術的に見ると、if、while、for、switchといったプログラム要素の多くはFunctorsを使ってモデル化することができます。この考え方は、連続的なif-else文、switch-case文、およびコレクションに対する操作でよく使われます。
このAPIに含まれる重要なインターフェイスはPredicateとClosureです。Predicateは条件を表現するのに使います。結果がtrueなら、Closureのexecuteメソッドが呼び出されます。
本稿で紹介するFunctorsサンプルは、データベースコマンドを処理する単純なスクリプト処理エンジンです。このサンプルでは、スクリプトコマンドに対してどのコマンドクラスを呼び出すかを定義したswitch-caseディシジョンツリーをFunctorsでモデル化しています。完全なコードは「in.co.narayanan.commons.collections.functors」パッケージに収められています。使用するデータベースコマンドの構文は次のとおりです。
add <tablename>, <column values> delete <tablename>, <key_column>, <value> modify <tablename>, <key_column>, <value>, <column name and new values>
このスクリプトエンジンは、addスクリプトコマンドが呼び出されたときに、データベースに対してSQLのINSERT文を実行する必要があります。他のコマンドも同様に処理する必要があります。ScriptCommandクラスはドメインオブジェクトであり、スクリプトコマンドとその引数を表現します。AddTask、DeleteTask、ModifyTaskの各クラスには、スクリプトコマンドのために実行するロジックが含まれています。このエンジンをモデル化するための従来の方法を理解するため、ProcessCommand1クラスから抜粋した次のコードをよく見てください。
public void process(ScriptCommand command) { switch(command.getType()) { case ADD: { executeAdd(command); } break; case MODIFY: { executeModify(command); } break; case DELETE: { executeDelete(command); } break; } }
ここでは、switch-caseブロック内でスクリプトコマンドの種類をチェックし、該当するコマンドを実行しています。
次のリストはProcessCommand2クラスからのもので、Functorsを使って同じ動作を実現できることを示しています。
public void start() { Map predicatesAndClosures = new Flat3Map(); predicatesAndClosures.put(new AddPredicate(), new AddClosure()); predicatesAndClosures.put( new ModifyPredicate(), new ModifyClosure()); predicatesAndClosures.put( new DeletePredicate(), new DeleteClosure()); Closure switchClosure = ClosureUtils.switchClosure(predicatesAndClosures); CollectionUtils.forAllDo(commands, switchClosure); } private static class AddPredicate implements Predicate { public boolean evaluate(Object object) { ScriptCommand command = (ScriptCommand)object; return command.getType() == CommandType.ADD ? true : false; } } private static class AddClosure implements Closure { public void execute(Object input) { ScriptCommand command = (ScriptCommand)input; AddTask add = new AddTask(); add.add(command.getTableName(), command.getArgs()); } }
AddPredicateはコマンドタイプを評価します。AddClosureにはaddスクリプトコマンドを実行するロジックが含まれています。プレディケイトとクロージャはマップされて、ClosureUtilsユーティリティクラス内のswitchClosureメソッドに渡されます。結果がtrueの場合、返されたSwitchClosure参照は、クロージャにマップされたプレディケイトと呼び出しを評価します。trueが返されるまで、マップ内の各プレディケイトについて、これが繰り返されます。
このサンプルは、クロージャを併用してプログラムフローを実現する方法を示しています。ここでは、上記のAddClosureとCommons Collections API内のSwitchClosureを一緒に使用しています。たとえば、SwitchClosure参照をIfClosureと使用することができます。
ビジネスアプリケーションでのFunctorsの一般的な用途は、一群のビジネスルールをドメインオブジェクトインスタンスのコレクションに適用し、ルール結果に関連してアクションをとることです。ルールはプレディケイトとしてモデル化し、アクションはクロージャとしてモデル化する必要があります。Jythonに統合したPythonスクリプティング言語を使用すれば、完全なルールエンジンを構築することができます。その場合、プレディケイトとクロージャを書くためにPythonスクリプトを使用する必要があります。
