第8章 例

本書の最後には、本書 で説明した一部機能の使用例を提供するチュートリアルがあります。本章の例を確認するには、http://download.jboss.org/drools/release/5.0.1.26597.FINAL/drools-5.0-examples.zip より Examples ZIP アーカイブファイルをダウンロードしてください。

8.1. HelloWorld の例

名前: HelloWorld の例
メインクラス: org.drools.examples.helloworld.HelloWorldExample
タイプ: Java アプリケーション
目的: 簡単な Rules の使用方法を示すチュートリアル
このチュートリアルは、ルール使用の例を提供し、MVFLEX 式言語と Java ダイアレクトを実証します。また、knowledge bases および sessions をビルドする方法を説明し、監査のロギングとデバッグの出力を実証します (両方とも他の例では省略されています)。
Knowledge Builder を使用して、Drools ルール言語 (DRL) ソースファイルを knowledge base が消費できる複数の Package オブジェクトへ変換します。
add メソッドは、Resource インターフェースと Resource Type の両方をパラメーターとして取ります。Resource インターフェースを使用して、ResourceFactory を介して DRL ソースファイルをクラスパスより読み出します。

注記

ここでは実証されていませんが、Resource を使用して DRL ファイルを URL アドレスなどの他の場所から読み出すことも可能です。必要に応じて複数のファイルを追加できます。
また、異なる名前空間を持つ DRL ファイルを追加することも可能です (この場合、Knowledge Builder は各名前空間に対して 1 つのパッケージを作成します)。異なる名前空間を持つ複数のナレッジパッケージを同じ knowledge base に追加することができます。
DRL をすべて追加したら、Knowledge Builder のエラーをチェックします (knowledge base がパッケージを検証しますが、文字列形式のエラー情報にしかアクセスできません。そのため、Knowledge Builder インスタンスよりデバッグを行う必要があります)。
エラーが修正されたら、Knowledge Builder コレクションを取得し、KnowledgeBaseFactory より Knowledge Builder をインスタンス化してナレッジパッケージのコレクションを追加します。

例8.1 HelloWorld の例: Knowledge Base および Session の作成

final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

// this will parse and compile in one step
kbuilder.add(ResourceFactory.newClassPathResource
	("HelloWorld.drl",HelloWorldExample.class), ResourceType.DRL);

// Check the builder for errors
if (kbuilder.hasErrors()) 
{
	System.out.println(kbuilder.getErrors().toString());
	throw new RuntimeException("Unable to compile \"HelloWorld.drl\".");
}

// get the compiled packages (which are serializable)
final Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();

// add the packages to a knowledgebase (deploy the knowledge packages).
final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(pkgs);

final StatefulKnowledgeSession ksession = 
	kbase.newStatefulKnowledgeSession();
		
		
		

JBoss Rulesイベントモデル は、独自の内部プロセスのほとんどを公開します。DebugAgendaEventListenerDebugWorkingMemoryEventListener の 2 つのデフォルトデバッグ listeners が提供されます。これらのリスナーは、デバッグ情報を Error Console へ出力します (listeners をセッションに追加するのは簡単です。この処理については、後で説明します)。
KnowledgeRuntimeLogger は、AgendaWorking Memory listeners から特別に派生されたものです。実行監査 を提供し、この出力はグラフィック表示されます。

重要

エンジンが実行を終了した時に logger.close() を呼び出す必要があります。

注記

本書の例では主に JBoss Rules の監査ロギング機能を使用し、今後の検査のために実行フローを記録します。

例8.2 HelloWorld の例: イベントのロギングと監査

// setup the debug listeners
ksession.addEventListener( new DebugAgendaEventListener() );
ksession.addEventListener( new DebugWorkingMemoryEventListener() );
        
// setup the audit logging
KnowledgeRuntimeLogger logger = 
	KnowledgeRuntimeLoggerFactory.newFileLogger(ksession,"log/helloworld");
これは、2 つのフィールドのみを持つ (文字列の message と、整数である HELLO または GOODBYE のどちらかになる status) 単一クラスの単純な例です。

例8.3 HelloWorld の例: メッセージクラス

public static class Message 
{
    public static final int HELLO   = 0;
    public static final int GOODBYE = 1;

    private String          message;
    private int             status; 
    ...
}

		
		
		

この例は、Hello World という言葉が含まれ、HELLO の状態を持つ単一の Message オブジェクトを作成します。作成後、このオブジェクトはエンジンへ挿入され、その時点で fireAllRules() が実行されます。

注記

ネットワーク評価はすべて挿入中に実行されます。そのため、実行しているプログラムが fireAllRules() メソッド呼び出しに到達するまでに、完全一致し、適切に実行できるルールをエンジンが認識します。

例8.4 実行

final Message message = new Message();
message.setMessage("Hello World");
message.setStatus(Message.HELLO);
ksession.insert(message);

ksession.fireAllRules();

logger.close();

ksession.dispose();
		
		
		

この例を Java アプリケーションとして実行するには、次の手順に従います。
  1. JBoss Rules IDEorg.drools.examples.helloworld.HelloWorldExample クラスを開きます。
  2. クラスを右クリックし、[Run as...] を選択した後、[context menu] から [Java application] を選択します。

注記

fireAllRules() にブレイクポイントを追加し、ksession 変数を選択すると、Hello World がすでにアクティベートされ、Agenda に追加されたことが分かるはずです (これにより、パターン一致の作業がすべて挿入中に実行されたことが確認されます)。
fireAllRules Agenda ビュー

図8.1 fireAllRules Agenda ビュー

アプリケーションの出力は System.out へ送られ、debug listener の出力は debug listener へ送られます。

例8.5 コンソールウインドウの System.out

Hello World
Goodbye cruel world

		
		
		

例8.6 コンソールウインドウの System.err

==>[ActivationCreated(0): rule=Hello World; 

tuple=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]]
[ObjectInserted: handle=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]; 
object=org.drools.examples.HelloWorldExample$Message@17cec96]
[BeforeActivationFired: rule=Hello World; 

tuple=[fid:1:1:org.drools.examples.HelloWorldExample$Message@17cec96]]

==>[ActivationCreated(4): rule=Good Bye; 
tuple=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]]
[ObjectUpdated: handle=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]; 
old_object=org.drools.examples.HelloWorldExample$Message@17cec96; 

new_object=org.drools.examples.HelloWorldExample$Message@17cec96]
[AfterActivationFired(0): rule=Hello World]
[BeforeActivationFired: rule=Good Bye; 

tuple=[fid:1:2:org.drools.examples.HelloWorldExample$Message@17cec96]]
[AfterActivationFired(4): rule=Good Bye]  

		
		
		

ルールの「左側」の部分 (when 以降) は、working memory への挿入時に、Message.HELLO 状態の各 Message オブジェクトに対してアクティベートされることを示しています。
また、コードの左側の部分は、message (message 属性へバインドされる) と m (一致した Message オブジェクト自体にバインドされる) の 2 つの変数バインディングが作成されるよう指示します。
右側 (then 以降) はルールの「結果」部分です。ルールの dialect 属性で宣言された通り、MVEL で書かれていることを確認してください。ルールのこの部分は、bound variable message の内容を System.out へ送信します。この後、MVEL の modify ステートメントより、 m にバインドされた message object に含まれる message および status 属性を変更します。このステートメントにより、割り当てのブロックを一度に適用できます (ブロックが完了すると、エンジンは自動的に変更を通知されます)。

例8.7 Hello World のルール

rule "Hello World"
	dialect "mvel"
when
	m : Message( status == Message.HELLO, message : message )
then
	System.out.println( message ); 
	modify (m) { message="Goodbye cruel world", status=Message.GOODBYE };
end
		
		
		

ルール結果の実行中に再度 Agenda ビューを確認するには、次の手順に従います。
  1. DRL ファイルの modify 呼び出し上にブレイクポイントを設定します。
  2. JBoss Rules IDEorg.drools.examples.HelloWorld クラスを開きます。
  3. コンテキストメニューへ移動し、[ Debug As...] をクリックした後に [JBoss Rules Application] を選択し、実行を開始します。
Good Bye という別のルールは Java を使用します。このルールはここでアクティベートされ、アジェンダに置かれます。
Hello World ルールの Agenda ビュー

図8.2 Hello World ルールの Agenda ビュー

Good Bye ルールは Hello World ルールと似ていますが、Message.GOODBYE 状態の Message オブジェクトと一致します。

例8.8 Good Bye ルール

rule "Good Bye"
	dialect "java"
when
	Message( status == Message.GOODBYE, message : message )
then
	System.out.println( message ); 
end
		
		
		

KnowledgeRuntimeLoggerFactory メソッドの newFileLogger を使用して KnowledgeRuntimeLogger を作成した Java コードをもう一度思い出してください。このコードは最後に logger.close() を呼び出しました。このようにして、Audit に表示される監査ログファイルが作成されました。

注記

Audit ビューは実行フローを表示するため多くの例で使用されます。
以下のスクリーンショットを見てください。これは以下を表しています。
  1. オブジェクトが挿入され、Hello World ルールのアクティベーションが作成されます。
  2. アクティベーションが実行され、Message オブジェクトが更新されます。その結果、Good Bye ルールがアクティベートされ、実行されます。
  3. Audit ビューでイベントを選択すると、元のイベントが緑色で強調表示されます (この例では、Activation executed イベントの元となる Activation created イベントが緑色で強調表示されます)。
Audit ビュー

図8.3 Audit ビュー

8.2. 状態の例

名前: 状態の例
状態クラス: org.drools.examples.state.StateExampleUsingSalience
タイプ: Java アプリケーション
Rules ファイル: StateExampleUsingSalience.drl
目的: 基本的なルールの使用とルール実行優先度における競合の解決方法を実証します。
この例には 3 つの異なる実装があり、各実装は 前向き連鎖 と呼ばれる同じ基本的な挙動を実装するための代替方法を実証します。前向き連鎖とは、working memory のファクトへの変更に基づいてルールを順に評価、アクティベート、および実行する engine の機能のことです。

8.2.1. 状態の例について

org.drools.examples.state.State クラスには、名前と現在の状態の 2 つのフィールドがあります。現在の状態は以下のいずれかになります。
  • NOTRUN
  • FINISHED

例8.9 状態クラス

public class State {
    public static final int NOTRUN   = 0;
    public static final int FINISHED = 1;

    private final PropertyChangeSupport changes =
        new PropertyChangeSupport( this );

    private String name;
    private int    state;

    ... setters and getters go here...
}
上記の例を見てください。org.drools.examples.state.State を無視すると (理由は後で説明します)、ABC、および D という名前の 4 つの State オブジェクトが作成されたことが分かります。当初これらの状態は、使用されるコンストラクターのデフォルト値である NOTRUN に設定されます。各インスタンスが順に session にアサートされ、fireAllRules() が呼び出されます。

例8.10 Salience 状態の実行例

State a = new State( "A" );
State b = new State( "B" );
State c = new State( "C" );
final State d = new State( "D" );

// By setting dynamic to TRUE, JBoss Rules will use JavaBean
// PropertyChangeListeners so you do not have to call modify or update().
boolean dynamic = true;

session.insert( a, dynamic );
session.insert( b, dynamic );
session.insert( c, dynamic );
session.insert( d, dynamic );

session.fireAllRules();
session.dispose(); // Stateful rule session must always be disposed when finished</programlisting>
      
アプリケーションを実行するには、次の手順に従います。
  1. JBoss Rules IDEorg.drools.examples.state.StateExampleUsingSalience クラスを開きます。
  2. class を右クリックし、[Run as...] を選択した後、[Java Application] を選択します。
JBoss Rules IDE コンソールウインドウに次の出力が表示されます。

例8.11 Salience 状態のコンソール出力

A finished
B finished
C finished
D finished
計 4 つのルールがあります。 Bootstrap ルールが最初に実行され、AFINISHED 状態に設定されると、B の状態も FINISHED になります。(CD は両方とも B に依存するため、一時的に競合が発生しますが、salience 値によって解決されます)。
次に、この処理が実行された方法を分析します。これには、監査ロギング 機能を使用します。この機能により、各操作の結果がグラフィック表示されます。次の手順に従って監査ログを取得します。
  1. Audit View が表示されていない場合、[Window] をクリックし、[Show View]、[Other...]、[JBoss Rules]、[Audit View] と順番に選択します。
  2. Audit ViewOpen Log ボタンをクリックし、drools-examples-drl-dir>/log/state.log というファイルを選択します。
この時点で、画面の Audit View は次のようになるはずです。
Salience 状態の Audit View 例

図8.4 Salience 状態の Audit View

Audit View のログを上から下まで読みます。各アクションと、working memory に書き込まれた変更が記録されているのが分かります。そのため、状態が NOTRUNState オブジェクト A をアサートすると、Bootstrap ルールがアクティベートされますが、他の State オブジェクトをアサートしてもすぐに影響はありません。

例8.12 Salience 状態: Bootstrap ルール

rule Bootstrap
when
	a : State(name == "A", state == State.NOTRUN )
then
	System.out.println(a.getName() + " finished" );
	a.setState( State.FINISHED );
end
Bootstrap ルールを実行すると、A の状態が FINISHED に変わり、A to B ルールがアクティベートされます。

例8.13 A to B ルール

rule "A to B"
when
	State(name == "A", state == State.FINISHED )
	b : State(name == "B", state == State.NOTRUN )
then
	System.out.println(b.getName() + " finished" );
	b.setState( State.FINISHED );
end
A to B ルールを実行すると B の状態が FINISHED に変わり、B to C ルールと B to D ルールの両方がアクティベートされ、アクティベーションが agenda に置かれます。
これ以降は、両方のルールを実行できるため、「競合」の状態にあると言えます。競合解決ストラテジーにより、engineagenda はどちらのルールを実行するか決定することができます。B to C ルールの salience 値の方が大きいため (10、デフォルト値は 0) 最初に実行され、オブジェクト CFINISHED 状態に設定されます。
上記の Audit View は、結果として 2 つのアクティベーションが競合状態になる A to B ルールの State オブジェクトへの変更を表しています。Audit View が開かれている間にルール内にデバッグポイントを配置することができるため、Agenda View を使用してアジェンダの状態を調査することも可能です。以下のスクリーンショットは、A to B ルールのブレークポイントを表しています。また、2 つのルールが競合状態にある時の agenda の状態も表しています。
状態の例: Agenda View

図8.5 状態の例: Agenda View

例8.14 B to C ルール

rule "B to C"
	salience 10
when
	State(name == "B", state == State.FINISHED )
	c : State(name == "C", state == State.NOTRUN )
then
	System.out.println(c.getName() + " finished" );
	c.setState( State.FINISHED );
end
B to D ルールは最後に実行され、オブジェクト D の状態を FINISHED に変更します。

例8.15 B to D ルール

rule "B to D"
when
	State(name == "B", state == State.FINISHED )
	d : State(name == "D", state == State.NOTRUN )
then
	System.out.println(d.getName() + " finished" );
	d.setState( State.FINISHED );
end
この時点で、実行するルールがないため engine が停止します。
この例で注意する点は、PropertyChangeListener オブジェクトに基づいた 動的ファクト です。エンジンが fact プロパティーの変更を「確認」し、反応するには、アプリケーションがこれらの情報をエンジンに通知する必要があります。これは、ルール (modify ステートメント) を介して明示的に行うか、暗黙的 (ファクトが実装された PropertyChangeSupport よりエンジンに通知) に行うことが可能です。

注記

PropertyChangeSupportJava Beans Specification に定義されます。
次の例では、PropertyChangeSupport を使用する方法について学びましょう (これを使用すると、ルールが modify ステートメントだらけにならないようにすることが可能です)。この機能を使用するには、org.drools.example.State クラスと同じ方法で、最初にファクトが PropertyChangeSupport を実装するようにします。その後、次のコードを使用してファクトを working memory に挿入します。

例8.16 動的ファクトの挿入

// By setting dynamic to TRUE, JBoss Rules will use JavaBean
// PropertyChangeListeners so that one does not have to call modify or update().
final boolean dynamic = true;

session.insert( fact, dynamic );
PropertyChangeListener オブジェクトが使用される場合、各 セッター は追加のコード (通知のため) を実装する必要があります。org.drools.examples クラスの State のセッターは次の通りです。

例8.17 「セッター」の PropertyChangeListener サポート

public void setState(final int newState) {
    int oldState = this.state;
    this.state = newState;
    this.changes.firePropertyChange( "state",oldState,newState );
}
この例には、他のクラスが 2 つ存在します。これらのクラスは StateExampleUsingAgendGroupStateExampleWithDynamicRules と呼ばれます。上記の通り、これらのクラスは A to B から C to D へ実行されます。
  • StateExampleUsingAgendGroup クラスは agenda groups クラスを使用してルールの競合を制御し、最初に実行するルールを決定します。
  • StateExampleWithDynamicRules クラスは、実行している working memory セッションへルールを追加する方法を表します。
アジェンダグループ を使用してアジェンダをグループに分割し、実行パーミッションを持つグループを決定します。デフォルトでは、すべてのグループが MAIN というアジェンダグループに含まれます。agenda-group 属性により、ルールの異なる agenda group を指定することができます。最初に、MAIN agenda groupworking memory によって使用されます。

重要

グループのルールは、グループがフォーカスを受け取る場合のみ実行されます。これには、setFocus() メソッドまたは auto-focus ルール属性を使用します (auto-focus ルール属性を使用する場合、ルールが一致し、アクティベートされると自動的にフォーカスが agenda group に設定されるため、auto-focus という名前になっています。このメソッドは、B to D ルールの前に B to C ルールを実行できるようにします)。

例8.18 アジェンダグループの状態の例: B to C ルール

rule "B to C"
      agenda-group "B to C"
      auto-focus true       
  when
      State(name == "B", state == State.FINISHED )      
      c : State(name == "C", state == State.NOTRUN )
  then
      System.out.println(c.getName() + " finished" );
      c.setState( State.FINISHED );
      kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "B to D" ).setFocus();
end
B to CB to D agenda group 上で setFocus() を呼び出します。これにより、アクティブなルールを実行でき、B to D に属するルールをトリガーします。

例8.19 アジェンダグループの状態の例: B to D ルール

rule "B to D"
	agenda-group "B to D"
when
	State(name == "B", state == State.FINISHED )      
	d : State(name == "D", state == State.NOTRUN )
then
	System.out.println(d.getName() + " finished" );
	d.setState( State.FINISHED );
end
StateExampleWithDynamicRules の例は、fireAllRules() の実行後、他のルールをベースに追加します。この新しいルールは他の 状態遷移 です。

例8.20 動的状態の例: D to E ルール

rule "D to E"
when
	State(name == "D", state == State.FINISHED )      
	e : State(name == "E", state == State.NOTRUN )
then
	System.out.println(e.getName() + " finished" );
	e.setState( State.FINISHED );
end
これは、出力の最後の部分を生成します。

例8.21 動的状態の出力例

A finished
B finished
C finished
D finished
E finished

8.3. フィボナッチの例

名前: フィボナッチ
メインクラス: org.drools.examples.fibonacci.FibonacciExample
タイプ: Java アプリケーション
Rules ファイル: Fibonacci.drl
目的: 再帰およびクロス積の一致を実証します。
フィボナッチ数 (http://en.wikipedia.org/wiki/Fibonacci_number) は、レオナルド・ダ・ピサが発見したゼロ と 1 で始まる数列です。その後のフィボナッチ数は、2 つの先行する数を足して取得します。よって、フィボナッチ数列は 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584 で始まり、無限に続きます。この例では、数列を使用して 再帰 を実証し、salience 値 使用して競合を解決する方法も実証します。
この例では Fibonacci 単一ファクトクラスを使用します。このクラスには sequencevalue の 2 つのフィールドがあります。sequence フィールドは、フィボナッチ数列のオブジェクトの位置を示すために使用されます。value フィールドは、特定の数列位置に対する Fibonacci オブジェクトの値を示します (算出する必要がある値を示すために -1 を使用)。

例8.22 フィボナッチクラス

public static class Fibonacci {
    private int  sequence;
    private long value;

    public Fibonacci( final int sequence ) {
        this.sequence = sequence;
        this.value = -1;
    }

    ... setters and getters go here...
}
次の手順に従って、例を実行します。
  1. JBoss Rules の統合開発環境で org.drools.examples.FibonacciExample クラスを開きます。
  2. このクラスを右クリックし、 [Run as...] を選択した後に [Java application] を選択します。
次の出力が JBoss Rules IDE Console ウインドウに表示されます ( ...snip... は行が省略されていることを示しています)。

例8.23 フィボナッチの例: コンソールの出力

recurse for 50
recurse for 49
recurse for 48
recurse for 47
...snip...
recurse for 5
recurse for 4
recurse for 3
recurse for 2
1 == 1
2 == 1
3 == 2
4 == 3
5 == 5
6 == 8
...snip...
47 == 2971215073
48 == 4807526976
49 == 7778742049
50 == 12586269025
Java から実行するには、数列フィールド fifty を持つ単一の Fibonacci オブジェクトを挿入します。その後、再帰ルールが実行され、他の 49 個のオブジェクトが自動的に挿入されます。

注記

この例では、PropertyChangeSupport を使用しません。この代わりに、MVFLEX 式言語を使用するため、modify キーワードを使用できます。このキーワードにより、ブロックセッターアクション を使用できます (エンジンに変更も通知します)。

例8.24 フィボナッチの例: 実行

ksession.insert( new Fibonacci( 50 ) );
ksession.fireAllRules();
Recurse ルールは非常に単純です。このルールは、-1 を値として持つアサートされた Fibonacci オブジェクトを照合するため、現在よりも 1 つ前の値を持つ新しい Fibonacci オブジェクトが作成され、アサートされます。数列フィールドが 1 である Fibonacci オブジェクトが存在しない限り、Fibonacci オブジェクトが追加されるたびにルールが実行されます。
50 個のフィボナッチオブジェクトがすべてメモリーに格納されると、ルールの照合を停止するために not 条件要素が使用されます。Bootstrap ルールを実行する前に 50 個の Fibonacci オブジェクトをすべてアサートする必要があるため、ルールは salience 値も持っています。

例8.25 フィボナッチの例:「再帰」ルール

rule Recurse
	salience 10
when
	f : Fibonacci ( value == -1 )
	not ( Fibonacci ( sequence == 1 ) )
then
	insert( new Fibonacci( f.sequence - 1 ) );
	System.out.println( "recurse for " + f.sequence );
end
Audit ビューは元のアサート (数列フィールドは 50) を表示します。その後、アサートされた各 Fibonacci オブジェクトによって Recurse ルールが何度も実行される、ルールの連続的な再帰を表します。
フィボナッチの例: Recurse Audit ビュー 1

図8.6 フィボナッチの例: Recurse Audit ビュー 1

sequence フィールドの値が 2 である Fibonacci オブジェクトがアサートされると、Bootstrap ルールと一致し、Bootstrap ルールと共にアクティベートされます。

注記

Bootstrap フィールドには複数の制限があります。これらの制限は、フィールドの値が 1 または 2 と等しいか確認します。

例8.26 フィボナッチの例: Bootstrap ルール

rule Bootstrap
when
	f : Fibonacci( sequence == 1 || == 2, value == -1 ) 
	// this is a multi-restriction || on a single field
then 
	modify ( f ){ value = 1 };
	System.out.println( f.sequence + " == " + f.value );
end
この時点で、アジェンダは下図のようになります。しかし、Recurse ルールの salience 値の方が大きいため、Bootstrap ルールは実行されません。
フィボナッチの例: Recurse Agenda ビュー 1

図8.7 フィボナッチの例: Recurse Agenda ビュー 1

sequence 値が 1 の Fibonacci オブジェクトがアサートされると、再度 Recurse ルールに対して一致し、2 度アクティベートされます。

注記

not 条件要素により、sequence 値が 1 である Fibonacci オブジェクトが存在すると即座にルールの一致が行われないようになるため、Recurse は一致せず、アクティベートされません。
フィボナッチの例: Recurse Agenda ビュー 2

図8.8 フィボナッチの例: Recurse Agenda ビュー 2

-1 と等しくない値を持つ 2 つのオブジェクトが存在すると、Calculate ルールが実行されます (Bootstrap ルールがオブジェクトに 12 から 1 の数列値を設定したことに注意してください)。
この時点で、working memory に 50 個の Fibonacci オブジェクトが存在します。適切な「3 つ組」を選択して順に値を計算します。
可能なクロス積を制限するフィールド制約を使用しないルールに 3 つのフィボナッチパターンが存在する場合、50x49x48 の可能な組み合わせが存在し、125,000 のルール実行が発生する可能性がありますが、これらのほとんどは正しくありません。この問題が発生しないようにするため、Calculate ルールはフィールド制約を使用して、3 つのフィボナッチパターンを正しい順番に制限します。この方法は クロス積の一致 と呼ばれます。この仕組みは次の通りです。
  1. 最初のパターンは、値が != -1 の Fibonacci オブジェクトを見つけ、パターンとフィールドの両方をバインドします。
  2. 2 つ目の Fibonacci オブジェクトも同じ事を行いますが、別のフィールド制約を追加します。これは、f1 にバインドされた Fibonacci オブジェクトよりも数列が大きくなるようにするためです。このルールが最初に実行される時、最初と 2 番目の数列のみ値が 1 になります。2 つの制約は、 f1 が最初の数列を参照し、f2 が 2 番目の数列を参照するようにします。
  3. 最後のパターンは、-1 と等しい値を持ち、 f2 に含まれる値よりも 1 大きい数列値を持つ Fibonacci オブジェクトを見つけます。
この時点で、使用可能なクロス積より 3 つの Fibonacci オブジェクトが適切に選択されます。そのため、3 つ目のオブジェクトの値 (f3 へバインドされる) を計算できます。

例8.27 フィボナッチの例: Calculate ルール

rule Calculate
    when
        // Bind f1 and s1
        f1 : Fibonacci( s1 : sequence, value != -1 )
        // Bind f2 and v2; refer to bound variable s1
        f2 : Fibonacci( sequence == (s1 + 1), v2 : value != -1 )
        // Bind f3 and s3; alternative reference of f2.sequence
        f3 : Fibonacci( s3 : sequence == (f2.sequence + 1 ), value == -1 )      
    then
        // Note the various referencing rechniques.
        modify ( f3 ) { value = f1.value + v2 };
        System.out.println( s3 + " == " + f3.value );
end 
Modify ステートメントは f3 にバインドされるオブジェクトの値を更新します。その結果、値が -1 と等しくない別の新しいオブジェクトが存在することになります。このオブジェクトが作成されると、Calculate ルールが再度一致します。その後、次のフィボナッチ数を処理します。次図の Audit ビューはこの処理の概要を表しています。これは、最後に実行される Bootstrap ルールがどのように Fibonacci オブジェクトを変更し、Calculate ルールをトリガーするかを表しています。その後、別の Fibonacci オブジェクトが変更され、Calculate ルールが再実行されます。このサイクルは、オブジェクトすべてに値が設定されるまで継続されます。
フィボナッチの例: Bootstrap Audit ビュー

図8.9 フィボナッチの例: Bootstrap Audit ビュー

8.4. 銀行取引のチュートリアル

名前: 銀行取引のチュートリアル
メインクラス: org.drools.tutorials.banking.BankingExamplesApp.java
タイプ: Java アプリケーション
Rules ファイル: org.drools.tutorials.banking.*.drl
目的: パターンの一致、基本的なソート、および計算ルールを実証します。
本チュートリアルは、複数の口座で入出金を処理する完全な個人銀行取引アプリケーションの開発プロセスを実証します。このプロセスのために作成された複数の設計パターンを使用します。

重要

RuleRunner クラスは、データのセットに対して 1 つまたは複数の DRL ファイルを実行して、ナレッジパッケージをコンパイルし、各実行に対して knowledge base を作成します。これは、テストおよびチュートリアルの目的で使用できますが、knowledge base を一度だけ構築してキャッシュする必要がある実稼働システムでは適切でないことに注意してください。

例8.28 銀行取引のチュートリアル: RuleRunner

public class RuleRunner {

	public RuleRunner() {}

	public void runRules(String[] rules,Object[] facts) throws Exception
	{
		KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
		KnowledgeBuilder kbuilder = 
			KnowledgeBuilderFactory.newKnowledgeBuilder();

		for ( int i = 0; i < rules.length; i++ ) {
			String ruleFile = rules[i];
			System.out.println( "Loading file: " + ruleFile );
			kbuilder.add( ResourceFactory.newClassPathResource
				( ruleFile, RuleRunner.class ),ResourceType.DRL );
		}

	   Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
       kbase.addKnowledgePackages( pkgs );
       StatefulKnowledgeSession ksession = 
			kbase.newStatefulKnowledgeSession();

		for ( int i = 0; i < facts.length; i++ ) {
			Object fact = facts[i];
			System.out.println( "Inserting fact: " + fact );
			ksession.insert( fact );
		}
		ksession.fireAllRules();
	}
}
最初の Java クラスは、Example.drl という単一の DRL ファイルをロードし、実行しますが、データは挿入しません。

例8.29 銀行取引のチュートリアル: Java の例 1

public class Example1 
{
	public static void main(String[] args) throws Exception 
	{
		new RuleRunner().runRules(new String[]{"Example1.drl"},
			new Object[0] );
	}
}
		
		
		

これは最初に実行する簡単なルールです。これは単一の eval 条件を持ち、常に true になります。そのため、常に一致し、起動後に一度「実行」します。

例8.30 銀行取引のチュートリアル: Example1.drl のルール

rule "Rule 01"   
when
	eval( 1==1 )
then
	System.out.println( "Rule 01 Works" );
end

		
		
		

出力は次の通りです。ルールは単一の print ステートメントに一致し、実行することを表しています。

例8.31 銀行取引のチュートリアル: Example1.java の出力

Loading file: Example1.drl
Rule 01 Works

		
		
		

次に、簡単なファクトをいくつかアサートし、出力します。

例8.32 銀行取引のチュートリアル: Java の例 2

public class Example2 
{
	private static Integer wrap(int i) {return new Integer(i);}
	
	public static void main(String[] args) throws Exception 
	{
		Number[] numbers = new Number[] {wrap(3), wrap(1), wrap(4), 
			wrap(1), wrap(5)};
		new RuleRunner().runRules(new String[] { "Example2.drl" },numbers);
	}
}
		
		
		

これは、特定のファクトを使用しませんが、その代わりに java.lang.Integer クラスのセットを使用します (コレクション番号が「fact」でも「thing 」でもないため、最良の方法ではありません。銀行の口座には残高を表す数字があります。そのため、この場合、口座が「fact」になります。しかし、最初の実証の目的を満たすには整数をアサートすれば十分です)。
次に、これらの数字を出力する簡単なルールを作成します。

例8.33 銀行取引のチュートリアル: Example2.drl のルール

rule "Rule 02"   
when
	Number( $intValue : intValue )
then
	System.out.println("Number found with value: " + $intValue); 
end
		
		
		

繰り返しになりますが、これは大変単純なルールです。数字である「ファクト」を識別し、出力します。ここでは、インターフェースの使用に注意してください。整数は挿入されますが、パターン一致エンジンは、アサートされたオブジェクトのインターフェースとスーパークラスを一致できます。
出力の最初にはロードされた DRL が表示されます。次に挿入されたファクト、その後に一致して実行されたルールが表示されます (挿入された数字は一致および実行されるため、出力されます)。

例8.34 銀行取引のチュートリアル: Example2.drl の出力

Loading file: Example2.drl
Inserting fact: 3
Inserting fact: 1
Inserting fact: 4
Inserting fact: 1
Inserting fact: 5
Number found with value: 5
Number found with value: 1
Number found with value: 4
Number found with value: 1
Number found with value: 3

		
		
		

数字のソートには、ルールを使用するよりも優れた方法がいくつもありますが、この例の後半で Cashflow クラスを日付順に適用するタスクがあるため、ここでプロセスについて説明します。

例8.35 銀行取引のチュートリアル: Example3.java

public class Example3 
{
    private static Integer wrap(int i) {return new Integer(i);}

	public static void main(String[] args) throws Exception 
	{
		Number[] numbers = new Number[] {wrap(3), wrap(1), wrap(4), 
			wrap(1), wrap(5)};

		new RuleRunner().runRules(new String[]{ "Example3.drl"},numbers);
	}
}
		
		
		

ここでは、ルールが若干異なります。

例8.36 銀行取引のチュートリアル: Example3.drl のルール

rule "Rule 03"   
when
	$number : Number( )
	not Number( intValue &lt; $number.intValue )
then
	System.out.println("Number found with value: "+$number.intValue() ); 
	retract( $number );
end
		
		
		

ルールの最初の行は数字を識別し、値を抽出します。2 番目の行は、最初のパターンで検出された数字よりも小さい数字が存在しないようにします。セットで最も小さな数字のみ一致することを想定するかもしれませんが、出力後に数字が取り消されると、最も小さい数字が削除され、2 番目に小さい数字が公開されます。
生成された出力は次の通りです (数字は数値的にソートされることに注目してください)。

例8.37 銀行取引のチュートリアル: Example3.java の出力

Loading file: Example3.drl
Inserting fact: 3
Inserting fact: 1
Inserting fact: 4
Inserting fact: 1
Inserting fact: 5
Number found with value: 1
Number found with value: 1
Number found with value: 3
Number found with value: 4
Number found with value: 5

		
		
		

次に、個人口座のルールを作成しましょう。最初に Cashflow クラスオブジェクトを作成します。

例8.38 銀行取引のチュートリアル: Cashflow クラス

public class Cashflow 
{
    private Date   date;
    private double amount;

    public Cashflow() {}

    public Cashflow(Date date,double amount) 
	{
    this.date = date;this.amount = amount;
	}

    public Date getDate()			{	return date;		}
    public void setDate(Date date)	{	this.date = date;	}

    public double getAmount() 				{	return amount;			}
    public void setAmount(double amount)	{	this.amount = amount;	}

    public String toString() 
	{
		return "Cashflow[date=" + date + ",amount=" + amount + "]";
	}
}
		
		
		

Cashflow クラスには、日付と金額の 2 つの簡単な属性があります。toString メソッドが追加されているため、出力することができます (浮動小数点形式は多くの数型を正確に表せないため、通貨単位に double 型を使用することは通常推奨されません)。
値を設定するために使用できる「オーバーロードされた」コンストラクターもあります。次の例は、それぞれ日付と金額が異なる 5 つの Cashflow オブジェクトを挿入します。

例8.39 銀行取引のチュートリアル: Example4.java

public class Example4 
{    
    public static void main(String[] args) throws Exception 
	{
        Object[] cashflows = {
            new Cashflow(new SimpleDate("01/01/2007"), 300.00),
            new Cashflow(new SimpleDate("05/01/2007"), 100.00),
            new Cashflow(new SimpleDate("11/01/2007"), 500.00),
            new Cashflow(new SimpleDate("07/01/2007"), 800.00),
            new Cashflow(new SimpleDate("02/01/2007"), 400.00),
        };
        
        new RuleRunner().runRules(new String[] {"Example4.drl"},cashflows;
	}
}
		
		
		

SimpleDate 「コンビニエンス」クラスは、入力文字列を取り日付形式を定義するコンストラクターを提供し、java.util.Date クラスを拡張します。コードは次の通りです。

例8.40 銀行取引のチュートリアル: クラス SimpleDate

public class SimpleDate extends Date 
{
    private static final SimpleDateFormat format = 
    	new SimpleDateFormat("dd/MM/yyyy");
    
    public SimpleDate(String datestr) throws Exception 
	{             
        setTime(format.parse(datestr).getTime());
	}
}
		
		
		

ここで、ソートされた cashflow がどのように出力されるかを学ぶため、cashflow ファイルを検証します。

例8.41 銀行取引のチュートリアル: Example4.drl のルール

rule "Rule 04"   
when
	$cashflow : Cashflow( $date : date, $amount : amount )
	not Cashflow( date &lt; $date)
then
	System.out.println("Cashflow: "+$date+" :: "+$amount);  
	retract($cashflow);
end
		
		
		

この時点で cashflow を識別し、日付と金額を抽出できます。ルールの 2 行目では、見つかった cashflow よりも日付が古い cashflow が存在しないようにします。結果として、ルールを満たす cashflow を出力して取り消し、日付が次に新しい cashflow を処理できるようにします。

例8.42 銀行取引のチュートリアル: Example4.java の出力

Loading file: Example4.drl
Inserting fact: Cashflow[date=Mon Jan 01 00:00:00 GMT 2007,amount=300.0]
Inserting fact: Cashflow[date=Fri Jan 05 00:00:00 GMT 2007,amount=100.0]
Inserting fact: Cashflow[date=Thu Jan 11 00:00:00 GMT 2007,amount=500.0]
Inserting fact: Cashflow[date=Sun Jan 07 00:00:00 GMT 2007,amount=800.0]
Inserting fact: Cashflow[date=Tue Jan 02 00:00:00 GMT 2007,amount=400.0]
Cashflow: Mon Jan 01 00:00:00 GMT 2007 :: 300.0
Cashflow: Tue Jan 02 00:00:00 GMT 2007 :: 400.0
Cashflow: Fri Jan 05 00:00:00 GMT 2007 :: 100.0
Cashflow: Sun Jan 07 00:00:00 GMT 2007 :: 800.0
Cashflow: Thu Jan 11 00:00:00 GMT 2007 :: 500.0

		
		
		

次に、TypedCashflow を作成するため Cashflow クラスを拡張します (入金または出金操作のいずれかになります)。通常、Cashflow に追加するのが最も簡単な方法ですが、ここでは以前のバージョンのクラスを保持するために拡張が使用されます。
public class TypedCashflow extends Cashflow {
    public static final int CREDIT = 0;
    public static final int DEBIT  = 1;

    private int type;

    public TypedCashflow() { }

    public TypedCashflow(Date date, int type, double amount) 
	{
        super( date, amount );
        this.type = type;
    }

    public int getType() 
	{
        return type;
    }

    public void setType(int type) 
	{
        this.type = type;
    }

    public String toString() 
	{
        return "TypedCashflow[date=" + getDate() 
			+ ",type=" + (type == CREDIT ? "Credit" : "Debit") 
			+ ",amount=" + getAmount() 
			+ "]";
    }
}
	
	
	

このコードはさまざまな方法で改善することが可能ですが、この例ではこのまま使用します。
次に、コードを実行するクラスを作成します。

例8.43 銀行取引のチュートリアル: Example5.java

public class Example5 
{    
    public static void main(String[] args) throws Exception 
	{      
        Object[] cashflows = {
            new TypedCashflow(new SimpleDate("01/01/2007"),
									TypedCashflow.CREDIT, 300.00),
            new TypedCashflow(new SimpleDate("05/01/2007"),
                              		TypedCashflow.CREDIT, 100.00),
            new TypedCashflow(new SimpleDate("11/01/2007"),
                              		TypedCashflow.CREDIT, 500.00),
            new TypedCashflow(new SimpleDate("07/01/2007"),
                              		TypedCashflow.DEBIT, 800.00),
            new TypedCashflow(new SimpleDate("02/01/2007"),
                              		TypedCashflow.DEBIT, 400.00),
        };
        
        new RuleRunner().runRules( 
			new String[] { "Example5.drl" }, cashflows );
    }
}
		
		
		

Cashflow オブジェクトのセットが作成され、各オブジェクトは入金または出金操作のいずれかになります。これらのオブジェクトは Example5.drl とともに RuleEngine へ提供されます。
次に、このルールを検証します。このルールはソートされた Cashflow オブジェクトを出力します。

例8.44 銀行取引のチュートリアル: Example5.drl のルール

rule "Rule 05"  
when
	$cashflow : TypedCashflow( $date : date, $amount : amount, 
		type == TypedCashflow.CREDIT )
	not TypedCashflow( date &lt; $date, type == TypedCashflow.CREDIT )
then
	System.out.println("Credit: "+$date+" :: "+$amount);
	retract($cashflow);
end
		
		
		

この時点で、CREDIT 型を持つ Cashflow ファクトを識別し、日付と金額を抽出することができます。ルールの 2 行目では、見つかった Cashflow よりも日付が古い同じ型の Cashflow が存在しないようにします。結果として、ルールを満たす Cashflow を出力して取り消し、日付が次に新しい CREDIT 型の Cashflow を処理できるようにします。
生成される出力は次の例の通りです。

例8.45 銀行取引のチュートリアル: Example5.java の出力

Loading file: Example5.drl
Inserting fact: TypedCashflow[date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0]
Inserting fact: TypedCashflow[date=Fri Jan 05 00:00:00 GMT 2007,type=Credit,amount=100.0]
Inserting fact: TypedCashflow[date=Thu Jan 11 00:00:00 GMT 2007,type=Credit,amount=500.0]
Inserting fact: TypedCashflow[date=Sun Jan 07 00:00:00 GMT 2007,type=Debit,amount=800.0]
Inserting fact: TypedCashflow[date=Tue Jan 02 00:00:00 GMT 2007,type=Debit,amount=400.0]
Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0
Credit: Fri Jan 05 00:00:00 GMT 2007 :: 100.0
Credit: Thu Jan 11 00:00:00 GMT 2007 :: 500.0

		
		
		

次に、2 つの銀行口座の入金と出金の両方を処理し、各口座の残高を計算します。最初に、Account オブジェクトを 2 つ作成し、RuleEngine へ渡す前に Cashflow クラスへインジェクトします (helper を用いずに正しい口座へ簡単にアクセスできるようにするため、この作業を行います)。
最初に Account クラスを見てみましょう。これは単純な Java オブジェクトで、口座番号と残高の両方を持ちます。
public class Account 
{
    private long   accountNo;
    private double balance = 0;

    public Account() { }

    public Account(long accountNo) 
	{
        this.accountNo = accountNo;
    }

    public long getAccountNo() 
	{
        return accountNo;
    }

    public void setAccountNo(long accountNo) 
	{
        this.accountNo = accountNo;
    }

    public double getBalance() 
	{
        return balance;
    }

    public void setBalance(double balance) 
	{
        this.balance = balance;
    }

    public String toString() 
	{
        return "Account[" + "accountNo=" + accountNo 
			+ ",balance=" + balance + "]";
    }
}
	
	
	

次に、AllocatedCashflow クラスを作成するため TypedCashflow を拡張し、Account 参照が含まれるようにします。

例8.46 AllocatedCashflow クラス

public class AllocatedCashflow extends TypedCashflow 
{
	private Account account;

	public AllocatedCashflow() {}

	public AllocatedCashflow(Account account, Date date, 
		int type, double amount) 
	{
		super( date, type, amount );
		this.account = account;
	}

	public Account getAccount() 
	{
		return account;
	}

	public void setAccount(Account account) 
	{
		this.account = account;
	}

	public String toString() 
	{
		return "AllocatedCashflow[" 
			+ "account=" + account 
			+ ",date=" + getDate() 
			+ ",type=" + (getType() == CREDIT ? "Credit" : "Debit") 
			+ ",amount=" + getAmount() 
			+ "]";
	}
}
		
		
		

Example5.java は 2 つの Account オブジェクトを作成します。コンストラクターの呼び出しにより Account オブジェクトの 1 つが各 Cashflow に渡されます。

例8.47 銀行取引のチュートリアル: Example5.java

public class Example6 
{    
	public static void main(String[] args) throws Exception 
	{      
		Account acc1 = new Account(1);
		Account acc2 = new Account(2);

		Object[] cashflows = 
		{
			new AllocatedCashflow(acc1,new SimpleDate("01/01/2007"), 
											TypedCashflow.CREDIT, 300.00),
			new AllocatedCashflow(acc1,new SimpleDate("05/02/2007"),
											TypedCashflow.CREDIT, 100.00),
			new AllocatedCashflow(acc2,new SimpleDate("11/03/2007"),
											TypedCashflow.CREDIT, 500.00),
			new AllocatedCashflow(acc1,new SimpleDate("07/02/2007"),
											TypedCashflow.DEBIT,  800.00),
			new AllocatedCashflow(acc2,new SimpleDate("02/03/2007"),
											TypedCashflow.DEBIT,  400.00),
			new AllocatedCashflow(acc1,new SimpleDate("01/04/2007"),    
											TypedCashflow.CREDIT, 200.00),
			new AllocatedCashflow(acc1,new SimpleDate("05/04/2007"),
											TypedCashflow.CREDIT, 300.00),
			new AllocatedCashflow(acc2,new SimpleDate("11/05/2007"),
											TypedCashflow.CREDIT, 700.00),
			new AllocatedCashflow(acc1,new SimpleDate("07/05/2007"),
											TypedCashflow.DEBIT,  900.00),
			new AllocatedCashflow(acc2,new SimpleDate("02/05/2007"),
											TypedCashflow.DEBIT,  100.00)
		};

		new RuleRunner().runRules(new String[]{"Example6.drl"},cashflows);
	}
}
		
		
		

ここで、Example6.drl ファイルのルールを確認して、各 Cashflow がどのように日付順で適用されるか確認し、残高を計算および出力します。
rule "Rule 06 - Credit"  
when
	$cashflow : AllocatedCashflow( $account : account,
		$date : date, $amount : amount, type==TypedCashflow.CREDIT )
	not AllocatedCashflow( account == $account, date < $date)
then
	System.out.println("Credit: " + $date + " :: " + $amount);     
	$account.setBalance($account.getBalance()+$amount);
	System.out.println("Account: " + $account.getAccountNo() +
		" - new balance: " + $account.getBalance());          
	retract($cashflow);
end

rule "Rule 06 - Debit"  
when
	$cashflow : AllocatedCashflow( $account : account,
	$date : date, $amount : amount, type==TypedCashflow.DEBIT )
	not AllocatedCashflow( account == $account, date < $date)
then
	System.out.println("Debit: " + $date + " :: " + $amount);      
	$account.setBalance($account.getBalance() - $amount);
	System.out.println("Account: " + $account.getAccountNo() +
		" - new balance: " + $account.getBalance());           
	retract($cashflow);
end
	
	
	

これにより、入金と出金に対して異なるルールが存在することになりますが、以前の Cashflow をチェックする時に型を指定しないでください。これは、型に関係なくすべての Cashflow が日付順にチェックされるようにするためです。作業する口座を識別するために conditions が使用され、Cashflow の金額で更新するために consequences が使用されます。
Loading file: Example6.drl
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Feb 05 00:00:00 GMT 2007,type=Credit,amount=100.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Sun Mar 11 00:00:00 GMT 2007,type=Credit,amount=500.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Wed Feb 07 00:00:00 GMT 2007,type=Debit,amount=800.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri Mar 02 00:00:00 GMT 2007,type=Debit,amount=400.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Sun Apr 01 00:00:00 BST 2007,type=Credit,amount=200.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Thu Apr 05 00:00:00 BST 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri May 11 00:00:00 BST 2007,type=Credit,amount=700.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon May 07 00:00:00 BST 2007,type=Debit,amount=900.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Wed May 02 00:00:00 BST 2007,type=Debit,amount=100.0]
Debit: Fri Mar 02 00:00:00 GMT 2007 :: 400.0
Account: 2 - new balance: -400.0
Credit: Sun Mar 11 00:00:00 GMT 2007 :: 500.0
Account: 2 - new balance: 100.0
Debit: Wed May 02 00:00:00 BST 2007 :: 100.0
Account: 2 - new balance: 0.0
Credit: Fri May 11 00:00:00 BST 2007 :: 700.0
Account: 2 - new balance: 700.0
Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0
Account: 1 - new balance: 300.0
Credit: Mon Feb 05 00:00:00 GMT 2007 :: 100.0
Account: 1 - new balance: 400.0
Debit: Wed Feb 07 00:00:00 GMT 2007 :: 800.0
Account: 1 - new balance: -400.0
Credit: Sun Apr 01 00:00:00 BST 2007 :: 200.0
Account: 1 - new balance: -200.0
Credit: Thu Apr 05 00:00:00 BST 2007 :: 300.0
Account: 1 - new balance: 100.0
Debit: Mon May 07 00:00:00 BST 2007 :: 900.0
Account: 1 - new balance: -800.0

	
	
	

8.5. 価格ルールのデシジョンテーブルの例

名前: 価格決定のポリシー例
メインクラス: org.drools.examples.decisiontable.PricingRuleDTExample
タイプ: Java アプリケーション
Rules ファイル: ExamplePolicyPricing.xls
目的: スプレッドシートベースのデシジョンテーブルの実証。
本チュートリアルは、スプレッドシートベースの デシジョンテーブル を使用して保険契約の小売価格を計算する方法を実証します。提供されているルールのセットは、特定の保険契約を申請するドライバーに対する基本価格と割引を計算します。基本保険料を決定するには、ドライバーの年齢、履歴、および契約タイプを考慮する必要があります。複数の追加ルールにより割引率を計算し、結果を絞り込みます。

8.5.1. 例の実行

PricingRuleDTExample.java ファイルを開き、Java アプリケーションとして実行します。次の出力が Console ウインドウに表示されます。
Cheapest possible
BASE PRICE IS: 120
DISCOUNT IS: 20
実行コードは標準パターンに準拠します。ルールがロードされ、ファクトが挿入され、さらに stateless session が作成されます。違いはルールが追加される方法にあります。
DecisionTableConfiguration dtableconfiguration =
    KnowledgeBuilderFactory.newDecisionTableConfiguration();
        dtableconfiguration.setInputType( DecisionTableInputType.XLS );

        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

        Resource xlsRes = ResourceFactory.newClassPathResource( "ExamplePolicyPricing.xls",
                                                                getClass() );
        kbuilder.add( xlsRes,
                      ResourceType.DTABLE,
                      dtableconfiguration );
DecisionTableConfiguration オブジェクトの使用に注目してください。この入力型は DecisionTableInputType.XLS に設定されます。

注記

Business Rules Management System を使用している場合、すべて自動的に設定されます。
この例では、DriverPolicy の 2 つの fact types が使用されます。両方のファクトタイプのデフォルト値が使用されます。Driver の年齢は 30 歳で、以前に保険の請求がなく、リスクプロファイルは LOW になります。ドライバーが申請している PolicyCOMPREHENSIVE で、まだ承認されていません。

8.5.2. ディシジョンテーブル

このデシジョンテーブルでは、各行が 1 つのルールを表し、各列は条件またはアクションのいずれかを表します。
デシジョンテーブルの設定

図8.10 デシジョンテーブルの設定

上記のスプレッドシートを見てください。RuleSet 宣言がパッケージ名を提供することに注意してください。Variables (グローバル変数用) や Imports (クラスのインポートに使用) など、他のオプション項目をここに追加することも可能です。この例では、ルールの名前空間は fact classes の名前空間と同じであるため、省略されています。
下の方に RuleTable 宣言があります。後続の Pricing Bracket が、生成されるルールの名前のプレフィックスとして割り当てられます。
その下に CONDITION または ACTION があります。これは、列の目的を示しています。列が条件の一部または生成されたルールの結果を形成するかどうかを決定します。
ドライバーのデータが 3 つのセルにまたがることを確認してください。各ファクトの下にあるテンプレート式はこのデータに適用されます。ドライバーの年齢範囲のデータは $1 および $2 (コンマ区切りの値で入力)、locationRiskProfile および priorClaims (それぞれの列に存在) を使用します。
基本保険料は action 列に設定されます。ここにメッセージのログを記録することも可能です。
基本保険料の計算

図8.11 基本保険料の計算

大まかなカテゴリー範囲は最も左にある列のコメントによって示されます。ドライバーと保険に関する既知の事実を用いて、手作業で基本コストを判断してみましょう。

注記

正解は Row Eighteen (このドライバーは過去に事故歴がないため) です。年齢は 30 歳で、基本価格は 120 になります。
割引の計算

図8.12 割引の計算

次に、上記の条件を基に割引を算出します。割引は、Age の範囲、過去の請求数、契約タイプなどの、要素の組み合わせによって判断されます。この例では、ドライバーは 30 歳で、過去の請求歴がなく、COMPREHENSIVE 契約を申請しています。これにより、20 パーセントの割引が適応されます。

注記

割引情報は同じワークシートの別のテーブルに保存されます。そのため、異なるテンプレートを適用できます。

重要

デシジョンテーブルがルールを生成することを理解することが重要となります。単に「トップダウン」論理は使用されません。ルールが作成されるデータをキャプチャーする方法として考えてみてください。この小さな違いがユーザーを混乱させることがあります。ルールの評価は、ルールエンジンの通常の仕組みが適用されるため、指定の順番で行われるとは限りません。

8.6. ペットストアの例

名前: ペットストア
メインクラス: org.drools.examples.petstore.PetStoreExample
タイプ: Java アプリケーション
Rules ファイル: PetStore.drl
目的: アジェンダグループ、グローバル変数、およびグラフィカルユーザーインターフェースの統合 (ルール内からのコールバックを含む) の使用について実証します。
この例は、グラフィカルユーザーインターフェース (この例では Swing ベースのデスクトップアプリケーション) を備えたプログラムとともにルールを使用する方法を示します。
指定時に実行を許可されるルールセットのメンバーを決定するため、Rules ファイル内には アジェンダグループ および 自動フォーカス 機能の使用方法を説明する例があります。さらに、この例は Java と MVFLEX 式言語のダイアレクトを組み合わせる方法も説明し、accumulate の使用やルールセット内から Java 関数を読み出す方法についても実証します。
Java コードはすべて PetStore.java ファイルに格納されます。このコードは次のプリンシパルクラスを定義します (このコードには、Swing イベントを処理するために使用されるマイナークラスも複数含まれます)。
  • Petstore - main() メソッドが含まれます。
  • PetStoreUI - Swing ベースのグラフィカルユーザーインターフェースを作成および表示します。マウスやボタンのクリックなど、主にさまざまな GUI イベントに応答する小型のクラスが複数含まれています。
  • TableModel - テーブルデータを保持します。Swing AbstractTableModel クラスを拡張する Java Bean として考慮してください。
  • CheckoutCallback - このクラスにより、グラフィカルユーザーインターフェースがルールと対話できます。
  • Ordershow - ユーザーが購入を希望するアイテムを保持します。
  • Purchase - 注文と購入済み製品の詳細を保存します。
  • Product - 購入可能な製品の価格情報やその他の情報を保持する Java Bean です。

注記

ほとんどのコードが Swing ベースまたはプレーン Java Beans 形式になります。ここでは Swing について詳しく説明しませんが、 Sun Microsystems の Web サイト http://java.sun.com/docs/books/tutorial/uiswing/ に使用方法のチュートリアルがあります。
ルールとファクトに関連する Petstore.java ファイルの Java コードの一部は次の通りです。

例8.48 PetStore.main にペットストアの RuleBase を作成

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

kbuilder.add( ResourceFactory.newClassPathResource( "PetStore.drl",
                                                    PetStore.class ),
              ResourceType.DRL );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );

// Create the stock.
Vector<Product> stock = new Vector<Product>();
stock.add( new Product( "Gold Fish", 5 ) );
stock.add( new Product( "Fish Tank", 25 ) );
stock.add( new Product( "Fish Food", 2 ) );

// A callback is responsible for populating the
// Working Memory and for firing all rules.
PetStoreUI ui = new PetStoreUI( stock,
                                new CheckoutCallback( kbase ) );
ui.createAndShowGUI();

		
		
		

上記のコードは、クラスパス上に存在する DRL ファイルからルールをロードします。ファクトが即座にアサートされ実行される他の例とは異なり、この例ではファクトのアサートと実行が遅延されます。これは、Vector オブジェクトである stock を許可するコンストラクターを使用して PetStoreUI オブジェクトが作成される最後から 2 つ目の行によって決定されます。これは製品と、この直前にロードされた rule-base が含まれる CheckoutCallback クラスのインスタンスを収集します。
ルールを実行する実際の Java コードは CheckoutCallBack.checkout() メソッドによって呼び出されます。これは、ユーザーが Checkout ボタンをクリックするとトリガーされます。
public String checkout(JFrame frame, List<Product> items) {
    Order order = new Order();

    // Iterate through list and add to cart
    for ( Product p: items ) {
        order.addItem( new Purchase( order, p ) );
    }

    // Add the JFrame to the ApplicationData to allow for user interaction

    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
    ksession.setGlobal( "frame", frame );
    ksession.setGlobal( "textArea", this.output );

    ksession.insert( new Product( "Gold Fish", 5 ) );
    ksession.insert( new Product( "Fish Tank", 25 ) );
    ksession.insert( new Product( "Fish Food", 2 ) );

    ksession.insert( new Product( "Fish Food Sample", 0 ) );

    ksession.insert( order );
    ksession.fireAllRules();

    // Return the state of the cart
    return order.toString();
}

	
	
	

2 つのアイテムがこのメソッドを介して渡されます。1 つは出力されたテキストフレーム (グラフィカルユーザーインターフェースの最下部) を囲む JFrame Swing コンポーネントで、もう 1 つは注文商品のリストです。画面の右上にある Table エリアに表示される情報を保持する TableModel からこのリストが取得されます。
for ループは注文商品のリストを Order Java Bean に変換します (PetStore.java にあります)。

注記

ルールの Swing データセットを直接参照することはできますが、このように単純な Java オブジェクトを使用する方法が推奨されます。このメソッドを使用すると、例を web アプリケーションに変換したい場合に Swing にバインドされなくなります。

注記

この例に示されている ステート はすべて Swing コンポーネントに格納されます。ルール自体は実質的に「ステートレス」です。Checkout ボタンがクリックされるたびに Swing TableModel の内容がセッションの working memory にコピーされます。
このコード内には、working memory への呼び出しが 9 つあります。最初の呼び出しは新しい working memory ステートフルナレッジセッション (Knowledge Base 内) を作成します (この Knowledge Base は、main() メソッドで作成された CheckoutCallBack クラスで渡されることに注意してください)。
これに続く 2 つの呼び出しは、ルールによってグローバル変数として保持される 2 つのオブジェクトを渡します。これらのオブジェクトは Swing テキストエリアと Swing フレームで、メッセージの書き込みに使用されます。
さらなる 挿入 によって、製品自体の情報が working memory と注文リストの両方に格納されます。最後の呼び出しは標準の fireAllRules() メソッドです。次に、ルールが実行された時にこのメソッドが何を行うか見てみましょう。

例8.49 パッケージ、インポート、グローバル、およびダイアレクト - PetStore.drl より抜粋

package org.drools.examples

import org.drools.WorkingMemory
import org.drools.examples.PetStore.Order
import org.drools.examples.PetStore.Purchase
import org.drools.examples.PetStore.Product
import java.util.ArrayList
import javax.swing.JOptionPane;

import javax.swing.JFrame 
        
global JFrame frame 
global javax.swing.JTextArea textArea

		
		
		

PetStore.drl ファイルの最初の部分には標準の package および import ステートメント (さまざまな Java クラスをルールが使用できるようにする) が含まれています。この他に、2 つの グローバル変数frame および textArea があります。これらのグローバル変数は、SwingJFrame コンポーネントおよび Textarea コンポーネントへの参照を保持します (Rules 変数は実行後すぐに期限切れとなりますが、グローバル変数はセッションの有効期間中、値が保持されます)。
次の例は PetStore.drl ファイルの最後の部分から抜粋したものです。これには、ルールによって参照される 2 つの関数が含まれています (これら 2 つの関数については次の項で取り上げます)。
function void doCheckout(JFrame frame, WorkingMemory workingMemory) 
{
	Object[] options = {"Yes","No"};
     
	int n = JOptionPane.showOptionDialog(frame,
		"Would you like to checkout?","",
		JOptionPane.YES_NO_OPTION,
		JOptionPane.QUESTION_MESSAGE,
		null,options,options[0]);

	if (n == 0) {workingMemory.setFocus( "checkout" );}   
}

function boolean requireTank(JFrame frame, WorkingMemory workingMemory, 
	Order order, Product fishTank, int total) 
{
	Object[] options = {"Yes","No"};
     
	int n = JOptionPane.showOptionDialog(frame,
		"Would you like to buy a tank for your " + 
		total + " fish?",
		"Purchase Suggestion",
		JOptionPane.YES_NO_OPTION,
		JOptionPane.QUESTION_MESSAGE,
		null,options,options[0]);

	System.out.print( "SUGGESTION: Would you like to buy a tank for your "
		+ total + " fish? - " );

	if (n == 0) {
		Purchase purchase = new Purchase( order, fishTank );
		workingMemory.insert( purchase );
		order.addItem( purchase );
		System.out.println( "Yes" );
	} else {
		System.out.println( "No" );
	}      
	return true;
}
	
	
	

注記

これらの関数を Rules ファイルに格納すると、ペットストアの例がコンパクトになります。実際には、同じルールパッケージ内の個別のファイルに関数を格納するか、標準の Java クラスの静的メソッドとして個別のファイルに関数を格納する必要があります。これらの関数は import function my.package.Foo.hello でインポートします。
これら 2 つの関数の目的は次の通りです。
  • doCheckout() は、ユーザーに支払い (チェックアウト) するかどうかを尋ねるダイアログボックスを表示します。支払いしたい場合、フォーカスが checkOut アジェンダグループに設定され、そのグループ内のルールに潜在的な実行パーミッションが与えられます。
  • requireTank() は、水槽を購入したいかどうかをユーザーに尋ねるダイアログボックスを表示します。購入したい場合、working memory の注文リストに追加されます。
これらの関数を呼び出すルールについては本チュートリアルの後半で説明します。今後の例は、ペットストアルールから派生します。最初の抽出は最初に実行されます (auto-focus 属性が true に設定されていることが部分的に関係します)。

例8.50 アイテムをワーキングメモリーに格納 - PetStore.drl ファイルより抜粋

/// Insert each item in the shopping cart into the Working Memory 
// Insert each item in the shopping cart into the Working Memory
rule "Explode Cart"
    agenda-group "init"
    auto-focus true
    salience 10
    dialect "java"
when
    $order : Order( grossTotal == -1 )
    $item : Purchase() from $order.items
then
    insert( $item );
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "show items" ).setFocus();
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "evaluate" ).setFocus();
end

		
		
		

このルールは、総合計 (PetStore.drl) が算出されていないすべての注文に対して一致します。順番に各購入アイテムをループします。ルール名、salience (ルールが実行される順番を提示)、ダイアレクト (Java に設定) など、Explode Cart ルールにはすでに説明した項目があります。また、ルールには新しいアイテムが 3 つあります。
  • agenda-group "init" - アジェンダグループの名前です。この例では、グループに 1 つのルールしか存在しません。しかし、Java コードとルール結果はどちらもフォーカスをこのグループに設定しないため、実行する機会を得られるかどうかは次の属性に依存します。
  • auto-focus true は、Java コードから fireAllRules() が呼び出された時に、アジェンダグループの唯一のルールが実行する機会を得られるようにします。
  • drools.setFocus()show items および evaluate アジェンダグループに順番にフォーカスを提供し、ルールの実行を許可します (実際には、確実に注文アイテムがメモリーに挿入されるよう、注文アイテムはすべてループされます。他のルールはその後順次実行されます)。
次の 2 つのリストは、show items および evaluate アジェンダグループのルールを表しています。

例8.51 GUI のアイテムを表示 - PetStore.drl ファイルより抜粋

rule "Show Items"
	agenda-group "show items"
	dialect "mvel"
when
	$order : Order( )
	$p : Purchase( order == $order )
then
	textArea.append( $p.product + "\n");
end
		
		
		

show items アジェンダグループが最初に呼び出されます。これには、Show Items (小文字と大文字の違いに注意してください) と呼ばれるルールのみがあります。このルールは、各購入のログ詳細をテキストエリア (GUI 画面の下部) へ移動します (この操作に使用される textArea 変数は先に説明したグローバル変数の 1 つです)。
evaluate アジェンダグループも先にリストされた explode cart よりフォーカスを取得します。このアジェンダグループには、Free Fish Food Sample Suggest Tank の 2 つのルールがあります。

例8.52 Evaluate アジェンダグループ: PetStore.drl ファイルより抜粋

// Free Fish Food sample when we buy a Gold Fish if we have not already 
//bought Fish Food and dont already have a Fish Food Sample
rule "Free Fish Food Sample"
	agenda-group "evaluate"
	dialect "mvel"
when
	$order : Order()
	not ( $p : Product( name == "Fish Food") &&
	Purchase( product == $p ) )
		not ( $p : Product( name == "Fish Food Sample") && 
		Purchase( product == $p ) )
		exists ( $p : Product( name == "Gold Fish") && 
		Purchase( product == $p ) )
		$fishFoodSample : Product( name == "Fish Food Sample" );
then
	System.out.println( "Adding free Fish Food Sample to cart" );
	purchase = new Purchase($order, $fishFoodSample);
	insert( purchase );
	$order.addItem( purchase ); 
end

// Suggest a tank if we have bought more than 5 gold fish and do not 
// already have one
rule "Suggest Tank"
	agenda-group "evaluate"
	dialect "java"
when
	$order : Order()
	not ( $p : Product( name == "Fish Tank") && 
	Purchase( product == $p ) )
	ArrayList( $total : size &gt; 5 ) from collect( Purchase
	( product.name == "Gold Fish" ) )
	$fishTank : Product( name == "Fish Tank" )
then
	requireTank(frame, drools.getWorkingMemory(), 
	$order, $fishTank, $total); 
end

		
		
		

Free Fish Food Sample ルールは以下の場合のみ実行されます。
  • ユーザーが魚の餌を持っていない場合。
  • ユーザーが魚の餌の無料サンプルを持っていない場合。
  • ユーザーの注文に金魚が含まれる場合。
これらの条件が満たされると、ルールが実行され、新しい商品 (Fish Food Sample) が生成され、Fish Food Sample の注文に追加されます。
同様に、Suggest Tank ルールは次の 2 つの条件が満たされた場合のみ実行されます。
  • ユーザーが水槽を注文していない場合。
  • ユーザーが金魚関係の製品を 6 つ以上注文した場合。
これらの条件が満たされると、ルールが実行され、ユーザーにダイアログボックスを順番に表示する requireTank() 関数が呼び出されます。確認後、水槽が注文に追加されます。ルールが requireTank() 関数を呼び出す時、ルールがグローバル frame 変数を渡すことに注意してください。これは、関数が Swing グラフィカルユーザーインターフェースのハンドルを持つからです。
次のルールは do checkout と呼ばれます。

例8.53 チェックアウトの実行: PetStore.drl ファイルより抜粋

rule "do checkout"
	dialect "java"
when
then
	doCheckout(frame, drools.getWorkingMemory());
end
		
		
		

do checkout ルールには設定されたアジェンダグループや auto-focus 属性がありません。そのため、デフォルトのアジェンダグループの一部と見なされ、明示的なフォーカスを受け取るよう設定されたルールがすべて完了したときに、フォーカスを自動的に受け取ります。
ルールには左側がないため、常に右側が doCheckout() 関数を呼び出します。この場合、ルールはグローバル frame 変数を渡し、Swing グラフィカルユーザーインターフェースのハンドルを関数に付与します (上記の通り、doCheckout() 関数はユーザーに確認ダイアログボックスを表示します。確認後、関数はフォーカスを checkout アジェンダグループに渡し、次のルールセットを実行できるようにします)。

例8.54 チェックアウトのルール: PetStore.drl ファイルより抜粋

rule "Gross Total"
	agenda-group "checkout"
	dialect "mvel"
when
	$order : Order( grossTotal == -1)
	Number( total : doubleValue ) from accumulate( Purchase ( $price : product.price ),sum( $price ) )
then
	modify( $order ) { grossTotal = total };
	textArea.append( "\ngross total=" + total + "\n" );
end

rule "Apply 5% Discount"
	agenda-group "checkout"
	dialect "mvel"
when
	$order : Order( grossTotal &gt;= 10 &amp;&amp; &lt; 20 )
then
	$order.discountedTotal = $order.grossTotal * 0.95;
	textArea.append("discountedTotal total="+$order.discountedTotal+"\n");
end

rule "Apply 10% Discount"
	agenda-group "checkout"
	dialect "mvel"
when
	$order : Order( grossTotal &gt;= 20 )
then
	$order.discountedTotal = $order.grossTotal * 0.90;
	textArea.append("discountedTotal total="+$order.discountedTotal+"\n");
end
		
		
		

checkout アジェンダグループには 3 つのルールがあります。
  • Gross Total - まだ実行されていない場合、製品価格を累積し、合計を working memory に保存して、Swing Text Area に表示します (textArea グローバル変数を使用)。
  • 総合計が 10 と20 の間になる場合、Apply 5% Discount が割引後の合計を計算し、working memory へ追加してテキストエリアに表示します。
  • 総合計が 20 を越える場合、Apply 10% Discount が割引後の合計を計算し、working memory へ追加してテキストエリアに表示します。
これが理論的なコードの仕組みの概要になります。ここで、実際に何が起こるか確認する必要があります。PetStore.java というファイルには main() メソッドが含まれているため、コマンドラインまたは IDE 内より標準の Java アプリケーションとして実行することができます (クラスパスが適切に設定されていることを前提とします)。
最初の画面には Pet Store Demo が含まれます。これには使用可能な製品 (左上) のリスト、選択された製品の空白のリスト (右上)、チェックアウトおよびリセットボタン (真ん中)、空白のシステムメッセージエリア (最下部) が含まれます。
起動直後の Pet Store Demo

図8.13 起動直後の Pet Store Demo

この時点に到達するまで、次のイベントが発生します。
  1. main() メソッドが実行され、ルールベースがロードされますが、ルールはまだ実行されません (ここまで、このコードがルールと何らかの関係がある唯一のコードになります)。
  2. 新しい PetStoreUI オブジェクトが作成され、ルールベースのハンドルが付与されます。このハンドルは後で使用します。
  3. さまざまな Swing コンポーネントが操作を実行します。この時点で初めて上記の画面が表示され、ユーザーによる入力を待ちます。
リストでさまざまな製品をクリックし、以下に似た画面が表示されるか確認します。
製品が選択された Pet Store Demo

図8.14 製品が選択された Pet Store Demo

注記

繰り返しになりますが、ルールコードはまだ実行されていません。これは Swing コードで、マウスクリックイベントを「リッスン」し、選択された製品を TableModel オブジェクトへ追加して右上の部分に表示します (これは Model View Controller 設計パターンの従来の実装であることに注意してください)。
Checkout がクリックされた時のみビジネスルールが実行され、実行順は前述の順番とほぼ同じになります。
  1. Checkout ボタンがクリックされると、Swing クラスによって CheckOutCallBack.checkout() メソッドが呼び出されます。このメソッドは TableModel オブジェクト (グラフィカルユーザーインターフェースの右上に表示されます) よりデータを挿入し、さらにセッションの working memory に置きます。その後、ルールが実行されます。
  2. Explode Cart ルールは、自動フォーカス設定が true であるため、最初に実行されます。カート内のすべての製品をループし、確実にこれらの製品が working memory に存在するようにします。次に、Show Items および Evaluation アジェンダグループに実行パーミッションを与えます。これらのグループにあるルールは、カートの内容をテキストエリア (ウインドウの下部) に追加します。さらに、魚の餌をユーザーに無料で提供するか決定し、水槽を購入するかどうかをユーザーに尋ねます。水槽購入の画面は次のようになります。
    水槽を購入しますか?

    図8.15 水槽を購入しますか?

  3. Do Checkout ルールが次に実行されます。これは、現在フォーカスを持つアジェンダグループが他になく、デフォルトのアジェンダグループの一部であるためです。このルールは、次の質問が含まれるダイアログボックスを表示する doCheckout() 関数を常に呼び出します。
    "Would you like to Checkout?"
    doCheckout() 関数はフォーカスを checkout アジェンダグループに設定し、このグループのルールが実行する選択肢を与えます。
  4. checkout アジェンダグループのルールは「カート」の内容を表示し、適切な割引を適用します。
  5. 下図の通り、さらに製品をチェックアウトするか (これによりルールが再度実行されます) またはグラフィカルユーザーインターフェースが閉じられるかに応じて、Swing はユーザーの入力を待ちます。
ルールがすべて実行され、アプリケーションが終了

図8.16 ルールがすべて実行され、アプリケーションが終了

System.out 呼び出しをさらに追加して、イベントの流れを実証することもできます。上記の例に表された Console の出力は次のようになります。

例8.55 ペットストア GUI 実行後のコンソール出力

Adding free Fish Food Sample to cart 
SUGGESTION: Would you like to buy a tank for your 6 fish? - Yes

		
		
		

8.7. 数独の例

名前: 数独
メインクラス: org.drools.examples.sudoku.Main
タイプ: Java アプリケーション
Rules ファイル: sudokuSolver.drl, sudokuValidator.drl
目的: 論理的な問題を解決する方法を実証し、複雑なパターンマッチングを使用する方法を説明します。
この例では、JBoss Rules を使用して、複数の制約から派生される潜在的に大きな ソリューションスペース で回答を見つける方法を実証します。また、callbacks を使用して、実行時の working memory への変更に基づいて画面を更新し、 JBoss Rules とグラフィカルユーザーインターフェースベースのアプリケーションを統合する方法についても説明します。

8.7.1. 数独の概要

数独は、日本で生まれた論理ベースの数字配置パズルです。このパズルの目的は、各列、行、および 3x3 の「ゾーン」に 1 から 9 までの数字が一度だけ含まれるよう、9x9 の正方形グリッドを埋めることです。
プレイヤーには、一部が完成されたグリッドと、ルールを守りながら完成させるタスクが与えられます。
プレイヤーが追加する新しい数字は、特定の行、列、および 3x3 のブロックで同時に一意である必要があります。

8.7.2. 例の実行

前述の手順に従って drools-examples ファイルをダウンロードし、インストールします。その後、 java org.drools.examples.sudoku.Main を実行します (この例には Java 5 が必要です)。比較的簡単な一部が埋められたグリッドがウインドウに表示されます。
一部が埋められたグリッド

図8.17 一部が埋められたグリッド

Solve ボタンをクリックすると rules engine が残りの値を埋めます。Console はルールが実行されるとルールの詳細情報を表示し、パズルの解き方を実証します。
Rule #3 determined the value at (4,1) could not be 4 as this value already exists in the same column at (8,1)
Rule #3 determined the value at (5,5) could not be 2 as this value already exists in the same row at (5,6)
Rule #7 determined (3,5) is 2 as this is the only possible cell in the column that can have this value
Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell.
Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell.
... 
Rule #3 determined the value at (1,1) could not be 1 as this value already  exists in the same zone at (2,1)
Rule #6 determined (1,7) is 1 as this is the only possible cell in the row that can have this value
Rule #1 cleared the other PossibleCellValues for (1,7) as a ResolvedCellValue of 1 exists for this cell.
Rule #6 determined (1,1) is 8 as this is the only possible cell in the row that can have this value
「ロジックの解決」ルールがすべてアクティベートおよび実行されると、engine は 2 番目の rule base を処理します。これにより、解答が完全で有効であることがチェックされます。この例では、すべてが完全で有効であるため、結果として Solve ボタンが無効になり、以下のメッセージが表示されます。
Solved (1052ms)
解決済みのグリッド

図8.18 解決済みのグリッド

この例にはロードおよび解決可能な複数のグリッドが含まれています。[File]、[Samples]、[Medium] の順にクリックし、より難易度が高いグリッドをロードします (新しいグリッドがロードされると、Solve ボタンが再度有効になることに注目してください)。
このグリッドを少し試した後、 [File]、 [Samples]、[!DELIBERATELY BROKEN!] の順にクリックし、意図的に無効なグリッドをロードします。間違いを見つけてみてください (たとえば、最初の行に 5 が 2 つあります)。
無効なグリッド

図8.19 無効なグリッド

無効であることを確認した後、Solve ボタンをクリックし、無効なグリッドに「ルールの解決」が適用された場合に何が起こるか見てみます。
解答が無効であることを示すために Solve ボタンのラベルが変更され、Validation Rule Set が発見した問題をすべて Console に出力することを確認してください。
There are two cells on the same column with the same value at (6,0) and (4,0)
There are two cells on the same column with the same value at (4,0) and (6,0)
There are two cells on the same row with the same value at (2,4) and (2,2)
There are two cells on the same row with the same value at (2,2) and (2,4)
There are two cells on the same row with the same value at (6,3) and (6,8)
There are two cells on the same row with the same value at (6,8) and (6,3)
There are two cells on the same column with the same value at (7,4) and (0,4)
There are two cells on the same column with the same value at (0,4) and (7,4)
There are two cells on the same row with the same value at (0,8) and (0,0)
There are two cells on the same row with the same value at (0,0) and (0,8)
There are two cells on the same column with the same value at (1,2) and (3,2)
There are two cells on the same column with the same value at (3,2) and (1,2)
There are two cells in the same zone with the same value at (6,3) and (7,3)
There are two cells in the same zone with the same value at (7,3) and (6,3)
There are two cells on the same column with the same value at (7,3) and (6,3)
There are two cells on the same column with the same value at (6,3) and (7,3)   
理論的に解決可能なパズルの解答によっては、現状態で engine が実際に見つけられないものがあります。例を確認するには、[File]、[Samples]、[Hard 3] の順にクリックします。一部が埋められたグリッドがロードされます。
ここで、Solve ボタンをクリックし、人間が解答を見つけられる可能性があっても、現在のルールがグリッドを完成できないことを確認してください。
これまで、プログラムはルールが 10 個のセットを使用して解答を推測しました。このルールセットを拡張し、このような難解度の高いパズルを解くために必要なロジックを engine に提供する必要があります。

8.7.3. Java ソースとルールの概要

Java ソースは /src/main/java/org/drools/examples/sudoku ディレクトリにあります。2 つの DRL ルール定義ファイルは /src/main/rules/org/drools/examples/sudoku ディレクトリにあります。
org.drools.examples.sudoku.swing パッケージには、数独パズルのフレームワークを実装するクラスセットが含まれています。

注記

このパッケージは JBoss Rules ライブラリに依存しません。
数独パズルを整数値の 9x9 グリッドとして保存するため、SudokuGridModel インターフェースを実装します (値はセルの値が決定していないことを示す null である場合があります)。
SudokuGridViewSwing コンポーネントです。SudokuGridModel の実装をグラフィカルに表すことができます。
SudokuGridEvent および SudokuGridListener はモデルのステート変更をビューに通知するため使用されます。セルの値が解決または変更されると、イベントが実行されます。JTable など、他の Swing コンポーネントで使用されるモデルビューコントローラーパターンの知識があるユーザーは、この挙動を理解できるはずです (このような概念を実証するため、SudokuGridSamples は部分的に完成されたパズルを複数提供します)。
org.drools.examples.sudoku.rules パッケージには、 JBoss Rules に基づいた SudokuGridModel の実装が含まれています。AbstractCellValue を拡張し、グリッドの特定セルの値を表す 2 つの Java オブジェクトが使用されます。この値には、セルの行と列の場所、値が含まれる 3x3 ゾーンのインデックス番号、およびセルに保持される実際の番号が含まれます。
PossibleCellValue はセルの値が現在不明であることを示しています (どのセルにも 2 から 9 個の可能な値があります)。
ResolvedCellValue は、セルの値が決定されたことを示します。セルの解決された値は 1 つのみです。
DroolsSudokuGridModelSudokuGridModel を実装します。これは、一部指定されたセルのアレイ (最初は 2 次元) を CellValue Java オブジェクトのセットに変換します。これにより、solverSudoku.drl ルールファイルに基づいて working memory セッションが作成されます。また、CellValue オブジェクトも working memory に挿入します。
solve() メソッドが呼び出されると、パズルを解こうとする fireAllRules() が呼び出されます。
DroolsSudokuGridModelWorkingMemoryListenerworking memory にアタッチします。これにより、パズルが解決された時に挿入および取り消しイベントで呼び戻すことが可能です。新しい ResolvedCellValueworking memory に挿入されると、このコールバックにより実装は SudokuGridEventSudokuGridListener クライアントに対して実行することができ、リアルタイムで更新します。
"solver" working memory によってルールがすべて実行されると、DroolsSudokuGridModel が 2 番目のルールセットを実行します (これらのルールは validatorSudoku.drl ファイルから取得されます)。これらのルールは同じ Java オブジェクトのセットと動作し、結果となるグリッドが有効で完全な解答であるかを判断することが目的です。

注記

org.drools.examples.sudoku.Main クラスは上記のコンポーネントを組み合わせる Java アプリケーションを実装します。
org.drools.examples.sudoku パッケージには、solverSudoku.drlvalidator.drl の 2 つの DRL ファイルが含まれています。solverSudoku.drl は数独パズルを解くために使用されるルールを定義します。validator.drl は、working memory の現在の状態が有効な解答を表すかどうかをテストするルールを定義します。これらのルールセットはPossibleCellValue および ResolvedCellValue オブジェクトをファクトとして使用します。また、両方とも実行時に Console ウィンドウへデータを出力します。

注記

実際には、このように Console を使用するのではなく、ロギング情報を挿入し、WorkingMemoryListener を使用して出力をユーザーに表示します。

8.7.4. 検証ルール

validatorSudoku.drl ファイルにある検証ルールが従うプロセスは次の通りです。
  1. 最初のルールは、working memoryPossibleCellValue オブジェクトがないことを確認します (パズルが解かれると、各セルに ResolvedCellValue オブジェクト 1 つのみが存在するはずです)。
  2. 他の 3 つのルールは ResolvedCellValue オブジェクトすべてと一致し、$resolved1 という変数へバインドされます。そして、同じ値が含まれ、同じ行、列および 3x3 ゾーンにある ResolvedCellValues を探します。
  3. これらのルールが実行されると、解答が無効である理由を示すメッセージが文字列のグローバルリストに追加されます。DroolsSudokoGridModel はルールセットを実行する前にこのリストをインジェクトします (また、fireAllRules() を呼び出した後にこのリストが空であるかどうかを確認します。空でない場合、リストに全文字列を出力します。空でない場合、すべての文字列がリストに出力され、グリッドが解決していないことを示すフラグが設定されます)。

8.7.5. ルールの解決

これは、solverSudoku.drl ファイルにある「解決」ルールの前に実行されるさらに複雑なプロセスです。
  1. ルール #1 は、無効な解答が存在する行と列に対応する PossibleCellValuesworking memory を消去します。他の複数のルールは、セルに値が必要であると判断した後、特定行および列の working memoryResolvedCellValues を挿入します。
    このルールは重要であるため、他のルールよりも大きな salience の値が指定されます。そのため、左側が true になると、即座に activations がアジェンダの最上部に移動され、実行されます。これにより、他のルールが誤って実行されないようにします。
    このルールは ResolvedCellValue 上で update() も実行します (これは、 working memory にアタッチされた WorkingMemoryListenersJBoss Rules がイベントを送信するようにルールが変更されていなくても発生します。このようなイベントを実行すると、ルールがルール自体を更新できます)。これにより、グラフィカルユーザーインターフェースがグリッドの新しい状態を表示します。
  2. ルール #2 は、可能な値が 1 つのみであるグリッドのセルを識別します。when 句の最初の行は、working memoryPossibleCellValue オブジェクトすべてと一致します。2 行目は not キーワードの使用法を表しています。

    注記

    このルールは、異なる値を持つ同じ行と列の PossibleCellValue が他にない場合のみ実行されます。
    このルールが実行されると、その行と列の単一の PossibleCellValueworking memory から取り消され、同じ値の ResolvedCellValue に置き換えられます。
  3. ルール #3 は、ResolvedCellValue と同じ値がある場合に行から PossibleCellValues を削除します。つまり、解決された値がセルに含まれる場合、パズルのルールに従うため同じ値が含まれる同じ行の他のセルを消去する必要があります。when 句の最初の行は、working memoryResolvedCellValue オブジェクトをすべて見つけます。2 番目の行は、これらの ResolvedCellValue オブジェクトと同じ行と値を持つ PossibleCellValues を見つけます。 このような PossibleCellValues が見つかると、ルールがアクティベートされ、実行された時に取り消されます。これは、これらのセルに対する適切な解答ではないためです。
  4. ルール #4#5 はルール #3 と同じように動作しますが、ResolvedCellValues と競合する冗長な PossibleCellValues がないか列とゾーンをチェックします。
  5. ルール #6 は、セルの可能な値が該当する行に 1 つだけある場合をチェックします。左側の最初の行は working memoryPossibleCellValue ファクトすべてと一致し、結果を複数のローカル変数に格納します。2 行目は、同じ値を持つ他の PossibleCellValue オブジェクトが同じ行に存在しないことをチェックします。3 - 5 行目は、同じ値を持つ ResolvedCellValue が同じゾーン、行、または列に存在しないことをチェックします (これは、このルールが不完全な状態で実行されないようにするためです)。

    注記

    また、3 - 5 行目を行を削除し、ルール #3#4、および #5 の salience の値を大きくしてこれらのルールが常にルール #6#7、および #8 の前に実行されるようにすることも可能です。
    ルールが実行された場合、$possible はセルの値を表す必要があります。そのため、これを取り消し、同等の ResolvedCellValue に置き換えます (これはルール #2 のプロセスと同じです)。
  6. ルール #7#8 はルール #2 と同じように動作しますが、#7 はグリッドの該当列にある単一の PossibleCellValues をチェックし、#8 はグリッドの該当ゾーンにある PossibleCellValues をチェックします。
  7. ルール #9 は最も複雑です。これは「該当する値のペアが特定行の 2 つのセルにのみあり (たとえば、46 の値はセル [0,3] および [0,5] の最初の行のみに存在できることを判断しました)、このセルのペアが他の値を保持できない場合、4 が含まれるペアと 6 が含まれるペアがどれであるは分かりませんが、これらの値がこれらの 2 つのセルになければならないことはわかっています。そのため、これらの値が同じ行の他の場所で発生する可能性を排除することができます」ということです。
  8. ルール #10#11 は ルール #9 と同じように動作しますが、#10 は該当列に可能な値が 2 つのみ存在することをチェックし、#8 は該当ゾーンに可能な値が 2 つのみ存在することをチェックします。
さらに難解なパズルを解くため、さらに複雑な理由付けをカプセル化するカスタムルールをコーディングし、ルールセットを拡張します。次項では、この作業を行う方法について提案します。

8.7.6. 将来的な開発に関する提言

この例を開発する方法は複数あります。技術を身に付けるため、次の提言を演習として行うようにしてください。
  • Agenda Groups: 段階的な実行宣言ツール を使用します。この例では、「解決」 と 「検証」の 2 つの段階があることが分かるはずです。現在、これらは 2 つのルールベースファイルより実行されます。「解決」と「検証」のカテゴリーに分類し、すべてのルールに対してアジェンダグループを定義することが推奨されます。その後、すべてが単一の rule-base よりロードされます。engine はこれらを順番に即座に実行します。
  • 自動フォーカス: このメソッドを使用し、通常のルール実行の例外に対応します。現時点では、入力データーまたは解決ルールのどちらかに不整合があった場合、そのまま無駄にルールを実行せずに、即座に報告した方がよいでしょう。すでに単一の rule-base が作成されているため、パズルの一貫性の検証に使用される各ルールに対して auto-focus 属性を定義します。
  • 論理挿入: 不整合は working memory に誤ったデータがある場合のみ発生します。そのため、したがって、検証ルールが不整合を 理論的に挿入 すると言えます (誤ったデータが取り消されると、すぐに不整合はなくなります)。
  • session.iterateObjects(): 現時点では、グローバルリストは問題を記録するために使用されますが、session.iterateObjects( new ClassObjectFilter( Inconsistency.class ) ) を介して必要な問題を呼び出すよう stateful session に要求すると、より興味深いでしょう。inconsistency を使用すると、不正なセルに色 (赤など) を付けて見やすくすることもできます。
  • kcontext.getKnowledgeRuntime().halt(): ソフトウェアが検出したエラーを即座に報告した場合でも、ルールの評価を停止するようエンジンに伝える方法が必要です。これは、不整合が存在する時に halt() コードを呼び出すルールをプログラミングして作成します。
  • Queries: DroolsSudokuGridModelgetPossibleCellValues(int row, int col) メソッドを見てください。実際に必要な CellValue オブジェクトを探しながらすべての CellValue オブジェクトを繰り返すことが分かります。JBoss Rules クエリを使用すると、この処理の効率を向上することができます。必要なオブジェクトを返し、きれいに繰り返すクエリを定義します (必要に応じて他のクエリを定義します)。
  • サービスとしてのグローバル: この変更の主な目的は、これに続くものを容易にすることですが、これ自体にも便利です。サービスとしてのグローバル の使用法について学ぶには、最初に callback を設定することが理想的です。この変更は、該当のセルに対して ResolvedCellValue を見つける各ルールがグラフィカルユーザーインターフェースを「呼び出し」て、更新することができ、ユーザーにフィードバックを即座に提供することを意味します。また、最後に見つかったセルの番号を違う色に「塗り替え」、異なるルールの結果を素早く識別できるようにすることもできます。
  • ステップバイステップ実行: 即座にユーザーフィードバックを提供できるようコールバックを設定すると、JBoss Rules制約実行 機能を使用できます。グラフィカルユーザーインターフェースにボタンを追加し、クリックした時に fireAllRules( 1 ) を呼び出して 1 つのルールを実行します。このようにして、ユーザーはエンジンが行っていることをステップごとに確認できます。

8.8. 数字当て

名前: 数字当て
メインクラス: org.drools.examples.numberguess.NumberGuessExample
タイプ: Java アプリケーション
Rules ファイル: NumberGuess.drl
目的: ルールを整理するためルールフローを使用する方法を実証します。
本項では、Number Guess の例を用いて、ルールの実行順序を制御するための rule-flow の使用方法について学びましょう。ルールのグループを実行する順序を明確に示すため、標準的なワークフロー図が使用されます。

例8.56 数字当てルールベースの作成

final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.drl",
	ShoppingExample.class ),ResourceType.DRL );

kbuilder.add( ResourceFactory.newClassPathResource( "NumberGuess.rf",
	ShoppingExample.class ),ResourceType.DRF );

final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
		
		
		

このパッケージを作成し、add() メソッドを用いてルールをロードするプロセスは、前述の例と同じですが、任意の手順が 1 つ追加されます。この追加手順では、1 つの knowledge base でさまざまな rule-flow を使用する機能を指定するため、現在の rule-flowNumberGuess.rf 行を追加します (この手順を省略すると、knowledge base が前述の例と同じ方法で作成されます)。

例8.57 ルールフローの開始

final StatefulKnowledgeSession ksession = 
	kbase.newStatefulKnowledgeSession();
KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger
	(ksession, "log/numberguess");

ksession.insert( new GameRules( 100,5 ) );
ksession.insert( new RandomNumber() );
ksession.insert( new Game() );

ksession.startProcess( "Number Guess" );
ksession.fireAllRules();

logger.close();
ksession.dispose();
		
		
		

生成後、knowledge base を使用して「ステートフル」セッションを取得します。このセッションに ファクト (標準的な Java オブジェクトのこと) を挿入します。簡単にするため、この例ではすべてのクラスが NumberGuessExample.java という 1 つのファイルに含まれています。
  • GameRules クラスは最大範囲と回答可能な回数を提供します。
  • RandomNumber クラスは、自動的にゼロから 100 までの数字を生成し、getValue() メソッドを用いて挿入した後にルールで使用できるようにします。
  • Game クラスは以前回答した答えと、現在の回答数を追跡します。

重要

標準の fireAllRules() メソッドを呼び出す前に、startProcess() メソッドを用いて以前ロードされたプロセスを開始します。

注記

実際は、オブジェクトをさらに最終ステートで使用します (たとえば、この数字を最大スコアのテーブルに追加できるようにする回答数)。しかし、本チュートリアルでは、working memory セッションが dispose() メソッドへの呼び出しによって消去されるようにすれば十分です。
数字当ての例のルールフロー

図8.20 数字当ての例のルールフロー

上図を表示するには、JBoss Rules IDENumberGuess.rf ファイルを開きます。外見上、アイコンは JBoss jBPM Workflow のアイコンと大変似ていて (同じではない)、このイメージは標準的なフローチャートと非常に似ています。
この図を編集するには、画面の左にあるコンポーネントのメニューを使用します。このエリアは パレット と呼ばれます。図は、X-Stream より、人言が判読できる XML 形式で保存されます。
Properties ビューが表示されていない場合は、[Window] > [Show View] > [Other] の順に選択し、Properties ビューをクリックして表示します。これは、rule-flow 上のアイテムを選択する 前に 行います (または rule-flow の空白スペースをクリックした後)。その後、次のプロパティーが使用できるようになります。
文字当てルールフローのプロパティー

図8.21 文字当てルールフローのプロパティー

注記

意味のある情報を得るため、本チュートリアルの残りの作業を行う間、Properties ビューを継続的に確認することが推奨されます。現時点では、session.startprocess() メソッドが最初に実行した時に開始された rule-flow プロセスの ID 番号が表示されています。
Number Guessrule-flow のノード型は次の通りです。
  • start (緑の矢印) および end ノード (赤いボックス) は rule-flow が開始および終了する場所を示します。
  • RuleFlowGroup (シンプルな黄色のボックスによって示されます) は、本項の後半で説明する DRL ファイルの RuleFlowGroups へマップします。たとえば、フローが Too High RuleFlowGroup に達すると、補足的な ruleflow-group Too High 属性でマーク付けされたルールのみが実行を許可されます。
  • action nodes (歯車のようなエンブレムを持つ黄色のボックスで示されます) は、標準的な Java メソッドの呼び出しを実行できます。この例では、ほとんどの action nodesSystem.out.println を呼び出し、ユーザーに何が起こっているか伝えます。
  • Guess CorrectMore Guesses Join などの split および join nodes (青の楕円) は、制御のフローを分割 (さまざまな条件による) または再結合できる場所を示します。
  • arrows はさまざまなノード間のフローの方向を示します。
これらのノードは、ルールとともに動作し、文字当てゲームを形成します。たとえば、Guess RuleFlowGroup は、実行する Get User Guess ルールのみを許可します。これは、一致する ruleflow-group "Guess" の属性を持つ唯一のルールであるからです。

例8.58 ルールフローの特定場所でのみ実行されるルールの例

rule "Get user Guess"
    ruleflow-group "Guess"
    no-loop
    when
        $r : RandomNumber()
        rules : GameRules( allowed : allowedGuesses )
        game : Game( guessCount &lt; allowed )
        not ( Guess() )
    then
        System.out.println( "You have " + ( rules.allowedGuesses - game.guessCount )
                            + " out of " + rules.allowedGuesses
                            + " guesses left.\nPlease enter your guess from 0 to "
                            + rules.maxRange );
        br = new BufferedReader( new InputStreamReader( System.in ) );
        i = br.readLine();        
        modify ( game ) { guessCount = game.guessCount + 1 }
        insert( new Guess( i ) );
end

		
		
		

このルールの残りの部分は比較的簡単です。guessCountallowedGuesses の数よりも小さく (GameRules クラスより読み取り)、ユーザーが正しい数字を回答していない場合に、左側 (when 句) は RandomNumber オブジェクトが working memory に挿入されるたびにアクティベートされるよう指示します。
右側 ( 結果 と呼ばれ、キーワード then が使用されます) はユーザーにメッセージを出力し、System.in よりユーザーの応答が到達するのを待ちます。この入力を受け取った後 (return キーが押されるまで System.in がブロックします)、回答数と実際の回答を更新または変更し、これらが working memory で使用可能になります。
Rules の残りの部分は比較的簡単です。ダイレクトが MVEL に設定され、さまざまな Java クラスがインポートされることをパッケージが宣言します。このファイルには合わせて 5 つのルールが存在します。
  1. Get User Guess (上記で検証されたルール)
  2. 最大の回答を記録するルール
  3. 最小の回答を記録するルール
  4. 回答を検査し、不正解の場合にメモリーから取り消すルール。
  5. すべての回答が使用されたことをユーザーに通知するルール。
標準ルールと rule-flow との間を統合する 1 つのポイントが ruleflow-group 属性です。2 つ目のポイントは DRL ルールファイルと rule-flow.rf ファイルの間です。split nodes (青の楕円) はワーキングメモリーの値を使用して (ルールによって更新される) 実行するアクションのフローを決定します。この挙動を確認するには、Guess Correct Node をクリックします。Properties ビュー内より Constraints Editor を開きます (Constraints プロパティー行をクリックした後に表示される右側のボタンを押します)。以下が表示されます。
GuessCorrect ノードの制約を編集

図8.22 GuessCorrect ノードの制約を編集

Edit をクリックすると、以下のダイアログボックスが表示されます。Textual Editor の値は標準の左側のルール形式に従い、working memory のオブジェクトを参照できます。consequence (右側にコード化) として、左側の式が true である場合に制御のフローがこのノードに従います。
GuessCorrect の Constraints Editor

図8.23 GuessCorrectConstraints Editor

NumberGuess.java の例には main() メソッドが含まれるため、標準の Java アプリケーション (BASH から、または IDE 経由) として実行できます。典型的数字当てゲームでは次の対話が行われます (数字はユーザーによって入力されます)。

例8.59 数字当てプログラムが人間に勝った場合のコンソールの出力例

You have 5 out of 5 guesses left.
Please enter your guess from 0 to 100
50
Your guess was too high
You have 4 out of 5 guesses left.
Please enter your guess from 0 to 100
25
Your guess was too low
You have 3 out of 5 guesses left.
Please enter your guess from 0 to 100
37
Your guess was too low
You have 2 out of 5 guesses left.
Please enter your guess from 0 to 100
44
Your guess was too low
You have 1 out of 5 guesses left.
Please enter your guess from 0 to 100
47
Your guess was too low
You have no more guesses
The correct guess was 48 



		
		
		

重要なポイントは次のようになります。
  1. NumberGuessExample.javaMain() メソッドは RuleBase をロードして StatefulSession を取得し、GameGameRules、および RandomNumber (ターゲット番号が含まれる) オブジェクトを挿入します。このメソッドは使用されるプロセスフローを設定し、すべてのルールを実行します。そして、制御が RuleFlow へ移ります。
  2. NumberGuessExample.rf RuleFlowStart ノードで開始されます。
  3. 制御が Guess ノードに移り、More Guesses 結合ノードより移行します。
  4. Guess ノードで Get User Guess RuleFlowGroup が有効になります。この場合、Guess ルール (NumberGuess.drl ファイルに存在する) がトリガーされます。ユーザーにメッセージを表示し、応答を受け取って、メモリーに格納します。制御が次のノードである Guess Correct に移ります。
  5. このノードでは、制約が現在のセッションを検証し、取るべきパスを決定します。
    4. で回答した数字が大きすぎるまたは小さすぎる場合、フローは以下の両方を持つパスに移ります。
    • 通常の Java コードを使用して下記のステートメントのいずれかを出力するアクションノード。
      Too high
      Too low
    • Rules ファイル内より最大または最小の回答ルールがトリガーされるようにする RuleFlowGroup
    フローはこれらのノードから 6. で指定されたノードに移ります。
    4. の回答が正しい場合、通常の Java コードを持つアクションコードが次のステートメントを出力します。
    You guessed correctly.
    ここには、結合ノードがあるため (ルールフロー終了の直前) 、no-more-guesses パス (7.) が RuleFlow も終了します。
  6. 制御が結合ノードを介して RuleFlow へ移り、Guess Incorrect RuleFlowGroup (working memory より回答を取り消すようルールをトリガーします) へ移動します。その後、More Guesses 決定ノードへ移動します。
  7. More Guesses 決定ノード (ルールフローの右側) は制約を使用して (ルールが working memory に格納した値を確認して)、これ以上回答できるかどうかを決定します。回答可能な場合は 3. に戻ります。これ以上回答できない場合は、以下を表示するルールをトリガーする RuleFlowGroup を介して、制御がワークフローの最後に移動します。
    You have no more guesses
  8. 正しい数字が回答されるか、回答回数を使い果たすまで、3. から 7. のループが継続します。

8.9. Miss Manners とベンチマーキング

名前: Miss Manners
メインクラス: org.drools.benchmark.manners.MannersBenchmark
タイプ: Java アプリケーション
Rules ファイル: manners.drl
目的: Manners ベンチマークについて説明し、深さの競合解決 について取り上げます。

8.9.1. はじめに

Miss Manners はパーティーを開催する予定で、ホストとして座席を適切に配置したいと思っています。当初の計画では、ゲストを男女のペアで配置する予定でしたが、共通する話題がない可能性を心配しています。ホストとしてどうすべきでしょうか。Miss Manners は各ゲストの趣味に注目することにしました。性別が交互になるよう全員の座席を配置し、同じ趣味を持つ人が最低でも 1 名隣に座るようにします。

8.9.1.1. ベンチマーキングのスクリプト

  • Miss Manners深さ優先 探索を使用して座席の配置を決定します。男性と女性を交互に配置し、共通する趣味を持つ人が隣同士になるようにします。
  • Waltz は線画を 3 次元にします。3 次元にするため、線をラベル付けし、制約伝播 を使用します。
  • WaltzDBWaltz の汎用バージョンで、4 本以上の線による接合点をサポートし、データベースを使用します。
  • 自動ルートプランナー (ARP) は、ロボット車両向けに設計されたルートプランナーです。A* 探索アルゴリズムを使用します。
  • Weavera は、チャネルとボックス用の VLSI (Very Large Scale Integration) ルーターです。ブラックボード 技術を使用します。

注記

Miss Manners は、ルールエンジン業界の実質上の業界標準となるベンチマーキングテストです。この挙動は現在では広く知られるようになり、このテストを効率的に実行するよう多くのエンジンが最適化されたため、有用性は低くなっています。そのため、Waltz の人気が以前よりも高くなっています。

8.9.1.2. Miss Manners の実行フロー

最初の座席が割り当てられた後、システムは深さ優先再帰コードを実行します。これにより、最後の座席が割り当てられるまで、正しい座席の配置が繰り返し処理されます。Miss Mannerscontext インスタンスを使用し実行フローを制御します。次のアクティビティ図は、ルール実行と context の現状との関係を示すために分割されています。
Miss Manners のアクティビティ図

図8.24 Miss Manners のアクティビティ図

8.9.1.3. データと結果

ルールの詳細を説明する前に、アサートされたデータと結果となる座席配置を確認してください。データーは、性別が交互になるようにし、隣に共通の趣味を持つ人が座るよう配置された 5 人のゲストの簡単なセットです。

8.9.1.4. データ

データは OPS5 (Official Production System 5) 構文で提供されます。各属性は、名前と値のペアの括弧で囲まれたリストを持ちます。趣味は 1 人 1 つのみとなります。
(guest (name n1) (sex m) (hobby  h1)  )
(guest (name n2) (sex f) (hobby  h1)  )
(guest (name n2) (sex f) (hobby  h3)  )
(guest (name n3) (sex m) (hobby  h3)  )
(guest (name n4) (sex m) (hobby  h1)  )
(guest (name n4) (sex f) (hobby  h2)  )
(guest (name n4) (sex f) (hobby  h3)  )
(guest (name n5) (sex f) (hobby  h2)  )
(guest (name n5) (sex f) (hobby  h1)  )
(last_seat (seat 5)  )

8.9.1.5. 結果

結果リストの各行は、 Assign Seat ルールが実行されると出力されます。各行に前の行よりも 1 大きい pid 値 があることに注意してください (この重要性については、本項の後半の Assign Seat ルールの説明で取り上げます)。 lsrsln、および rn の略語は、左および右側の座席とこれらの座席に割り当てられたゲストの名前を表します。実際の実装では、長い属性名 (leftGuestName など) を使用しますが、本 ガイド では元の実装で使用された表記法が保持されます。
[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] 
[Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4] 
[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3] 
[Seating id=4, pid=3, done=false, ls=3, rn=n3, rs=4, rn=n2] 
[Seating id=5, pid=4, done=false, ls=4, ln=n2, rs=5, rn=n1]

8.9.2. 深い分析

8.9.2.1. 不正行為

Miss Manners は、クロス積の結合Agenda のアクティビティを実行するよう設計されています。これを理解していないユーザーが、パフォーマンスを改善するために例を「微調整」してしまうことがありますが、これにより Manners ベンチマークのポートは意味がなくなります。Miss Manners における既知の不正行為とポートエラーのリストは次の通りです。
  • ゲストの趣味を 1 つずつ 1 つのファクトとしてアサートせずに、アレイを使用。これにより、 cross products が大幅に減少します。
  • データシーケンスの変更。これにより、一致する量が減少し、実行速度が向上します。
  • テストアルゴリズムが first-best-match コードのみを使用するよう not 条件要素を変更。これは、基本的に 後向き連鎖 を実行するようテストテストアルゴリズムを変換します。このエリアの結果は、他の後向き連鎖ルールエンジンまたは Miss Manners のポートのみに相当します。
  • rule engine がゲストと座席を時期尚早に一致するよう、context を削除。適切なポートは、context start によりファクトが一致しないようにします。
  • rule engine組み合わせ パターンマッチングを実行しないようにする。
  • NOT CE の結果として 推論サイクル で取り消されるファクトがない場合、ポートが不完全。

8.9.2.2. 最初の座席の割り当て

コンテキストが START_UP に変わった後、アサートされたゲストごとに activations が作成されます。各 activation は単一の working memory アクションの結果として作成されるため、すべて同じ activation time タグを持ちます (アサートされる最後のゲストは、さらに大きな fact time タグを持ちます)。

注記

実行順序は、このルールにはほとんど影響しませんが、Assign Seat ルールには大きく影響します。
アクティベーションが実行され、最初の座席の配置と path がアサートされます。次に、findSeating ルールのアクティベーションを作成するため、Context 属性のステートが設定されます。
rule assignFirstSeat
when
	context : Context( state == Context.START_UP )
	guest : Guest()
	count : Count()
then
	String guestName = guest.getName();

	Seating seating =  new Seating(	count.getValue(),
									1, 
									true, 
									1, 
									guestName, 
									1, 
									guestName); 
	insert( seating );

	Path path = new Path( count.getValue(), 1, guestName );
	insert( path );

	modify( count ) { setValue ( count.getValue() + 1 )  }

	System.out.println( "assign first seat :  "+seating+" : "+path);

	modify( context ) { setState( Context.ASSIGN_SEATS ) } 
end

8.9.2.3. "findSeating" ルール

findSeating ルールは座席の配置を決定します。実行されると、アサートされた各ゲストに対して、アサートされた各座席配置の cross-product ソリューションを生成します (ルール自体とすでに割り当てられた選択済みソリューションを除く)。
rule findSeating
when 
	context : Context( state == Context.ASSIGN_SEATS )
	$s      : Seating( pathDone == true )
	$g1     : Guest( name == $s.rightGuestName )
	$g2     : Guest( sex != $g1.sex, hobby == $g1.hobby )
	count   : Count()
	not ( Path( id == $s.id, guestName == $g2.name) )
	not ( Chosen( id == $s.id, guestName == $g2.name, hobby == $g1.hobby) )
then
	int rightSeat = $s.getRightSeat();
	int seatId = $s.getId();
	int countValue = count.getValue();

	Seating seating = new Seating(	countValue,
									seatId,	
									false,
									rightSeat,
									$s.getRightGuestName(),
									rightSeat + 1,
									$g2.getName()
									);
	insert( seating );     

	Path path = new Path( countValue, rightSeat + 1, $g2.getName()  );
	insert( path );

	Chosen chosen = new Chosen( seatId, $g2.getName(), $g1.getHobby() );
	insert( chosen  );

	System.err.println( "find seating : "+seating+" : "+path+" : "+chosen);

	modify( count ) {setValue(  countValue + 1 )}        
	modify( context ) {setState( Context.MAKE_PATH )} 
end
=>[ActivationCreated(35): rule=findSeating 
[fid:19:33]:[Seating id=3, pid=2, done=true, ls=2, ln=n4, rs=3, rn=n3] 
[fid:4:4]:[Guest name=n3, sex=m, hobbies=h3] 
[fid:3:3]:[Guest name=n2, sex=f, hobbies=h3]

=>[ActivationCreated(35): rule=findSeating 
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] 
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1] 
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1] 

=>[ActivationCreated(35): rule=findSeating 
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] 
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1] 
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

注記

冗長な activations を作成することは意味がないように見えますが、Miss Manners の作成では優良なルール設計を目的としていないことを思い出してください。これは、cross-product matching 処理と Agenda 機能に対して完全にストレステストを行うために、意図的にルールセットが下手に記述されています。

注記

各アクティベーションの time タグ値は同じ 35 であることに注意してください。これは、ASSIGN_SEATSContext オブジェクトが変更になり、すべてアクティベートされたためです。OPS5 および LEX では、最後にアサートされた座席のアクティベーションが適切に実行されます。Depth では、accumulated fact time により、最後にアサートされる座席のアクティベーションが確実に実行されます。

8.9.2.4. "makePath" および "pathDone" ルール

makePath ルールは必ず pathDone の前に実行する必要があります。path オブジェクトは各座席の配置に対して最後までアサートされます (pathDone の条件は makePath のサブセットであることに注意してください。そのため、どのように makePath を最初に実行させるのか疑問に思う人がいるかもしれません)。
rule makePath
when 
	Context( state == Context.MAKE_PATH )
	Seating( seatingId:id, seatingPid:pid, pathDone == false )
	Path( id == seatingPid, pathGuestName:guestName, pathSeat:seat )
	not Path( id == seatingId, guestName == pathGuestName )
then
	insert( new Path( seatingId, pathSeat, pathGuestName ) );
end
rule pathDone
when
	context : Context( state == Context.MAKE_PATH ) 
	seating : Seating( pathDone == false ) 
then
	modify( seating ) {setPathDone( true )} 
	modify( context ) {setState( Context.CHECK_DONE)}
end
Rete 図

図8.25 Rete 図

最終的に、ルールは両方 Agenda と競合します。両方のルールは同一の activation time タグを持ちますが、makePath ルールの accumulate fact time タグの方が大きいため、makePath ルールが優先されます。

8.9.2.5. "Continue" および "Are We Done?" ルール

Are We Done は、最後の座席が割り当てられた時のみアクティベートします。この時点で、両方のルールが実行されます。makePath は常に pathDone よりも優先されますが、同じ理由で Are We DoneContinue よりも優先されます。
rule areWeDone
when
	context : Context( state == Context.CHECK_DONE ) 
	LastSeat( lastSeat: seat )
	Seating( rightSeat == lastSeat ) 
then
	modify( context ) {setState(Context.PRINT_RESULTS )}
end
rule continue
when
	context : Context( state == Context.CHECK_DONE )
then
	context.setState( Context.ASSIGN_SEATS );
	update( context );
end

8.9.3. 出力の概要

Assign First Seat

=>[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
=>[fid:14:14]:[Path id=1, seat=1, guest=n5]

==>[ActivationCreated(16): rule=findSeating
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

==>[ActivationCreated(16): rule=findSeating
[fid:13:13]:[Seating id=1 , pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]*

Assign Seating

=>[fid:15:17] :[Seating id=2 , pid=1 , done=false, ls=1, lg=n5, rs=2, rn=n4]
=>[fid:16:18]:[Path id=2, seat=2, guest=n4]
=>[fid:17:19]:[Chosen id=1, name=n4, hobbies=h1]

=>[ActivationCreated(21): rule=makePath 
[fid:15:17] : [Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4]
[fid:14:14] : [Path id=1, seat=1, guest=n5]*

==>[ActivationCreated(21): rule=pathDone
[Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4]*

Make Path

=>[fid:18:22:[Path id=2, seat=1, guest=n5]]

Path Done

Continue Process

=>[ActivationCreated(25): rule=findSeating
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4]
[fid:7:7]:[Guest name=n4, sex=f, hobbies=h3]
[fid:4:4] : [Guest name=n3, sex=m, hobbies=h3]*

=>[ActivationCreated(25): rule=findSeating
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4]
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1], [fid:12:20] : [Count value=3]

=>[ActivationCreated(25): rule=findSeating
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

Assign Seating

=>[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, lnn4, rs=3, rn=n3]]
=>[fid:20:27]:[Path id=3, seat=3, guest=n3]]
=>[fid:21:28]:[Chosen id=2, name=n3, hobbies=h3}]

=>[ActivationCreated(30): rule=makePath
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]
[fid:18:22]:[Path id=2, seat=1, guest=n5]*

=>[ActivationCreated(30): rule=makePath 
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]
[fid:16:18]:[Path id=2, seat=2, guest=n4]*

=>[ActivationCreated(30): rule=done 
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]*

Make Path

=>[fid:22:31]:[Path id=3, seat=1, guest=n5]

Make Path

=>[fid:23:32] [Path id=3, seat=2, guest=n4]

Path Done

Continue Processing

=>[ActivationCreated(35): rule=findSeating
[fid:19:33]:[Seating id=3, pid=2, done=true, ls=2, ln=n4, rs=3, rn=n3]
[fid:4:4]:[Guest name=n3, sex=m, hobbies=h3]
[fid:3:3]:[Guest name=n2, sex=f, hobbies=h3], [fid:12:29]*

=>[ActivationCreated(35): rule=findSeating 
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] 
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1]

=>[ActivationCreated(35): rule=findSeating 
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] 
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1], [fid:1:1] : [Guest name=n1, sex=m, hobbies=h1]

Assign Seating

=>[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]]
=>[fid:25:37]:[Path id=4, seat=4, guest=n2]]
=>[fid:26:38]:[Chosen id=3, name=n2, hobbies=h3]

==>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]
[fid:23:32]:[Path id=3, seat=2, guest=n4]*

==>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2] 
[fid:20:27]:[Path id=3, seat=3, guest=n3]*

=>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]
[fid:22:31]:[Path id=3, seat=1, guest=n5]*

=>[ActivationCreated(40): rule=done 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]*

Make Path

=>fid:27:41:[Path id=4, seat=2, guest=n4]

Make Path

=>fid:28:42]:[Path id=4, seat=1, guest=n5]]

Make Path

=>fid:29:43]:[Path id=4, seat=3, guest=n3]]

Path Done

Continue Processing

=>[ActivationCreated(46): rule=findSeating 
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] 
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1], [fid:2:2]
[Guest name=n2, sex=f, hobbies=h1]

=>[ActivationCreated(46): rule=findSeating 
[fid:24:44]:[Seating id=4, pid=3, done=true, ls=3, ln=n3, rs=4, rn=n2]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]*

=>[ActivationCreated(46): rule=findSeating 
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

Assign Seating

=>[fid:30:47]:[Seating id=5, pid=4, done=false, ls=4, ln=n2, rs=5, rn=n1]
=>[fid:31:48]:[Path id=5, seat=5, guest=n1]
=>[fid:32:49]:[Chosen id=4, name=n1, hobbies=h1]
本項の内容を学習し、Miss Manners ベンチマーキングスクリプトの仕組みや、注意すべき警告や落とし穴について理解できたと思います。

8.10. ライフゲーム (Conway's Game Of Life) の例

名前: Conway's Game Of Life
メインクラス: org.drools.examples.conway.ConwayAgendaGroupRun org.drools.examples.conway.ConwayRuleFlowGroupRun
タイプ: Java アプリケーション
Rules ファイル: conway-ruleflow.drl conway-agendagroup.drl
目的:
accumulatecollect、および from を実証します。
ライフゲーム (Conway's Game Of Life) はよく知られているシミュレーションモデルです。ここで使用するアプリケーションは Swing ベースの実装になります。この ゲーム を規定するルールは、JBoss Rules を使用して実装されます。ここでは、この実装の仕組みについて学びましょう。
最初に、以下のグリッドを見てください。ライフのシミュレーションが行われる「領域」を表すことで、ゲームをイメージしやすくなります。最初、グリッドは空であるため、システムには生きているセルは存在しません。各セルは「生」と「死」のいずれかとなり、生きているセルは緑色の丸で表されます。生きているセルの事前選択パターンは、Pattern ドロップダウンリストより選択できます (また、個々のセルをダブルクリックすると、「生」と「死」の状態を切り替えることができます)。
各セルは隣接するセルと関係していることを理解するのが重要になります。これは ゲーム のルールの基本となります。「隣接するセル」には、上下左右のセルだけでなく、対角線上に隣接するセルも含まれます。そのため、1 つセルに隣接するセルは 8 つあります。例外は、隣接するセルが 3 つしかない四隅のセルと、隣接するセルが 5 つのみの 4 辺上のセルになります。
新しいゲームの開始

図8.26 新しいゲームの開始

各世代ごとに (セルすべての完全な繰り返しと評価)、システムは進化し、セルが誕生したり死滅したりします。次世代がどのようになるかを規定する簡単なルールは次の通りです。
  • 生きているセルに隣接する生きたセルが 1 つ以下の場合、孤独により死滅します。
  • 生きているセルに隣接する生きたセルが 4 つ以上の場合、過密により死滅します。
  • 死んでいるセルに隣接する生きたセルがちょうど 3 つある場合、死んでいるセルが生き返ります。
これら基準のいずれかを満たさないセルは、次の繰り返しまでそのままの状態になります。これらの簡単なルールを考慮し、しばらくシステムでゲームを試してみてください。1 つずつ繰り返しを行い、ルールの適用を観察します。
進行中のゲーム

図8.27 進行中のゲーム

ここで、コードについて学びましょう (これは高度な例であるため、JBoss Rules フレームワークを理解していることが前提になります)。この例は、実行フローを管理する、AgendaGroups (ConwayAgendaGroupRun) と RuleFlowGroups (ConwayRuleFlowGroupRun) による 2 つの方法を表しています。これら方法を比較することは大変有益です。本章では、読者のほとんどが使用するルールフローバージョンについて説明します。
セルはすべて session に挿入されます。ルールフロープロセスは、register neighbour と呼ばれるグループのルールに実行パーミッションを与えます。ルールグループは Neighbour Relation クラスを使用して、北東、北、北西、西に隣接するセルを登録します。この関係は両方向であるため、南方向のセルにルールを作成する必要がないことに注意してください。また制約により、セルが最後から 1 つ前の列と一番上から 1 つ前の行に置かれることにも注意してください。アクティベーションがすべて実行されるまでに、すべてのセルが隣接するすべてのセルに関係します。

例8.60 隣接するセルの関係をすべて登録

      rule "register north east"
    ruleflow-group "register neighbour"
when
    CellGrid( $numberOfColumns : numberOfColumns )
    $cell: Cell( $row : row &gt; 0, $col : col &lt; 
    ( $numberOfColumns - 1 ) ) 
    $northEast : Cell( row  == ($row - 1), col == ( $col + 1 ) )    
then                    
    insert( new Neighbor( $cell, $northEast ) );
    insert( new Neighbor( $northEast, $cell ) );        
end

rule "register north"
    ruleflow-group "register neighbour"  
when
    $cell: Cell( $row : row &gt; 0, $col : col )   
    $north : Cell( row  == ($row - 1), col == $col )    
then        
    insert( new Neighbor( $cell, $north ) );
    insert( new Neighbor( $north, $cell ) );        
end

rule "register north west"
    ruleflow-group "register neighbour"
when
    $cell: Cell( $row : row &gt; 0, $col : col &gt; 0 )           
    $northWest : Cell( row  == ($row - 1), col == ( $col - 1 ) )                        
then        
    insert( new Neighbor( $cell, $northWest ) );
    insert( new Neighbor( $northWest, $cell ) );        
end

rule "register west"
    ruleflow-group "register neighbour"
when
    $cell: Cell( $row : row &gt;= 0, $col : col &gt; 0 )          
    $west : Cell( row  == $row, col == ( $col - 1 ) )                       
then        
    insert( new Neighbor( $cell, $west ) );
    insert( new Neighbor( $west, $cell ) );         
end

		
		
		

すべてのセルが挿入されると、Java コードが実行されます。さらに、パターンがグリッドに適用され、特定のセルが「生」の状態に設定されます。[Start] または [Next Generation] をクリックすると、Generation ルールフローが実行されます。このルールフローは繰り返しサイクルごとにセルの変更をすべて管理します。
生成ルールフロー

図8.28 生成ルールフロー

ルールフロープロセスは最初にルールの evaluate グループより実行されます。その結果、このグループのアクティブなルールはすべて実行可能になります (このグループのルールは、本項の最初に説明したメイン Game ルールを適用します。これらのルールは、死滅するセルと生き返るセルを決定します)。
phase 属性は、各セルがルールの特定グループへ応答する方法を指示します。通常、この属性はルールフロープロセスで定義された RuleFlowGroup の 1 つと関係します。
この属性は、現時点では実際にセルの状態を変更しません。これは、グリッドをすべて完全に評価してから編集内容を適用する必要があるからです。セルは一時的に Phase.KILL または Phase.BIRTH フェーズのいずれかに設定されます。これらのフェーズは後で変更を適用する時に使用されます。

例8.61 ステート変更でセルを評価

      rule "Kill The Lonely"
    ruleflow-group "evaluate"
    no-loop
when
#   A live cell has fewer than 2 live neighbors
    theCell: Cell(liveNeighbors < 2, cellState == 
    CellState.LIVE, phase == Phase.EVALUATE)then
    theCell.setPhase(Phase.KILL);
    update( theCell );
end

rule "Kill The Overcrowded"
    ruleflow-group "evaluate"
    no-loop
when
#   A live cell has more than 3 live neighbors
    theCell: Cell(liveNeighbors >; 3, cellState == 
    CellState.LIVE, phase == Phase.EVALUATE)then
    theCell.setPhase(Phase.KILL);
    update( theCell );
end

rule "Give Birth"
    ruleflow-group "evaluate"
    no-loop
when
#   A dead cell has 3 live neighbors
    theCell: Cell(liveNeighbors == 3, cellState == 
    CellState.DEAD, phase == Phase.EVALUATE)then
    theCell.setPhase(Phase.BIRTH);
    update( theCell );
end


		
		
		

セルがすべて評価された後、reset calculate ルールは、以前のデータ変更によって発生した計算アクティベーションを calculate グループから消去します。次に、「split」が入力されます。これにより、「kill」および「birth」グループの両方にあるアクティベーションを実行できます (これらのルールは状態の変更を適用します)。

例8.62 変更の適用

      rule "reset calculate"
    ruleflow-group "reset calculate"
when
then
    WorkingMemory wm = drools.getWorkingMemory();
    wm.clearRuleFlowGroup( "calculate" );
end

rule "kill"
    ruleflow-group "kill"
    no-loop
when
    theCell: Cell( phase == Phase.KILL )
then
    modify( theCell ){
        setCellState( CellState.DEAD ),
        setPhase( Phase.DONE );   
    }
end 
 
rule "birth"
    ruleflow-group "birth"
    no-loop
when
    theCell: Cell( phase == Phase.BIRTH )
then
    modify( theCell ){
        setCellState( CellState.LIVE ),
        setPhase( Phase.DONE );
    }

		
		
		

この時点で、複数のセルが変更されています。これは、複数のセルの状態が「live」または「dead」に変更されたためです。次に、隣接するすべてのセルを繰り返し処理するため neighbour relation が使用されるため、生きている隣接するセルの数が増加または減少します。セルの数が変わったセルはすべて evaluate フェーズに設定され、ルールフロープロセスの次の段階で評価されます。
手作業で繰り返し処理を実行する必要がないことに注意してください。ルールに関係を適用するだけで、必要なコードが最小限であってもルールエンジンはすべての処理を行います。「live」セルの数が決定され、設定されると、ルールプロセスは終了します。ルールフロープロセスに他の世代を評価するよう指示できますが、evaluate がクリックされた場合はエンジンがルールフロープロセスを再度実行します。

例8.63 ステート変更があったセルの評価

      rule "Calculate Live"
    ruleflow-group "calculate"
    lock-on-active  
when
    theCell: Cell( cellState == CellState.LIVE )
    Neighbor( cell == theCell, $neighbor : neighbor ) 
then
    modify( $neighbor ){
        setLiveNeighbors( $neighbor.getLiveNeighbors() + 1 ),
        setPhase( Phase.EVALUATE );   
    }
end 

rule "Calculate Dead"
    ruleflow-group "calculate"
    lock-on-active  
when
    theCell: Cell( cellState == CellState.DEAD )
    Neighbor( cell == theCell, $neighbor : neighbor )
then
    modify( $neighbor ){
        setLiveNeighbors( $neighbor.getLiveNeighbors() - 1 ),
        setPhase( Phase.EVALUATE );
    }
end