Java で Chain of Responsibility Pattern を 末尾再帰で実装した

はじめに

Chain of Responsibility Pattern という, マイナーな Gof のパターンがある.

本をよんでみて, これって再帰関数を利用すればもっとシンプルにかけるん じゃないかとおもって, 試してみた.

Chain of Responsibility Pattern

責務を持たせたオブジェクトの Chain に 要求を渡していく.

要求は,

  • そのオブジェクトで処理できればそこで処理する
  • そのオブジェクトで処理できなければ, 次のオブジェクトに渡す.

パターン未適用

[sourcecode language=”java” title=””]
import java.util.List;
import java.util.LinkedList;

public class ChainOfResponsibilitySample {
public static void main (String[] args) {
List chain = new LinkedList();
chain.add (new A ());
chain.add (new B ());
chain.add (new C ());

for (Handler handler : chain) {
if (handler.isMatch (‘b’)) {
handler.execute ();
break;
}
}
}
}

abstract class Handler {
public abstract boolean isMatch (char c);
public abstract void execute ();
}

class A extends Handler {
public boolean isMatch (char c) { return c == ‘a’; }
public void execute () { System.out.println ("a hit"); }
}

class B extends Handler {
public boolean isMatch (char c) { return c == ‘b’; }
public void execute () { System.out.println ("b hit"); }
}

class C extends Handler {
public boolean isMatch (char c) { return c == ‘c’; }
public void execute () { System.out.println ("c hit"); }
}
[/sourcecode]

絶望ポイント

ここがきたない.

制御側からいちいち判定用メソッドを読んだり, マッチしたらアクションを起動している.これが面倒.

[sourcecode language=”java” title=””]
for (Handler handler : chain) {
if (handler.isMatch (‘b’)) {
handler.execute ();
break;
}
}
[/sourcecode]

できれば, ひとつメソッドをよんだら, あとは好き勝手に処理されればいい.

Amazon で本を注文するときは, ポチったら, あとはコンビニに勝手に届いて入ればいい.

パターン適用

メリット

要求を出す側と, 要求を処理する側の結びつきが弱まる.

具体的にいえば, ループを回さなくてすむ.

コード

[sourcecode language=”java” title=””]
public class ChainOfResponsibilitySample {
public static void main (String[] args) {
Handler chain = new A (new B (new C (null)));
chain.handle (‘b’);
}
}

abstract class Handler {
private Handler next;

public Handler (Handler next) {
this.next = next;
}

public void handle (char c) {
if (isMatch (c))
execute ();
else
next.handle (c);
}

abstract boolean isMatch (char c);
abstract void execute ();
}

class A extends Handler {
public A (Handler next){ super (next); }
boolean isMatch (char c) { return c == ‘a’; }
void execute () { System.out.println ("a hit"); }
}

class B extends Handler {
public B (Handler next){ super (next); }
boolean isMatch (char c) { return c == ‘b’; }
void execute () { System.out.println ("b hit"); }
}

class C extends Handler {
public C (Handler next){ super (next); }
boolean isMatch (char c) { return c == ‘c’; }
void execute () { System.out.println ("c hit"); }
}
[/sourcecode]

感動のポイント

みよ! このシンプルさを.

[sourcecode language=”java” title=””]
public static void main (String[] args) {
Handler chain = new A (new B (new C (null)));
chain.handle (‘b’);
}
[/sourcecode]

こんなの, 関数型の考え方でかけば当たり前だ!

この主張をしたいがために, この記事を書いた.

関数型っぽくかけば, こんなの当たり前の方法.

[sourcecode language=”java” title=””]
public class ChainOfResponsibilityFinctional {
public static void main (String[] args) {

LinkedList chain = new LinkedList();
chain.add (new A ());
chain.add (new B ());
chain.add (new C ());

handle (chain, ‘b’);
}

static void handle (LinkedList chain, char c) {
Handler head = chain.element ();
chain.removeFirst ();
LinkedList tail = chain;
if (head == null)
return;
else {
if (head.isMatch (c)) {
head.execute ();
return;
}
else
handle (tail, c);
}
}
}

abstract class Handler {
public abstract boolean isMatch (char c);
public abstract void execute ();
}

class A extends Handler {
public boolean isMatch (char c) { return c == ‘a’; }
public void execute () { System.out.println ("a hit"); }
}

class B extends Handler {
public boolean isMatch (char c) { return c == ‘b’; }
public void execute () { System.out.println ("b hit"); }
}

class C extends Handler {
public boolean isMatch (char c) { return c == ‘c’; }
public void execute () { System.out.println ("c hit"); }
}
[/sourcecode]

感動のポイント

一行で一応処理できている.

[sourcecode language=”java” title=””]
handle (chain, ‘b’);
[/sourcecode]

末尾再帰を利用している. しかし, あんまりシンプルにかけないな…

[sourcecode language=”java” title=””]
static void handle (LinkedList chain, char c) {
Handler head = chain.element ();
chain.removeFirst ();
LinkedList tail = chain;
if (head == null)
return;
else {
if (head.isMatch (c)) {
head.execute ();
return;
}
else
handle (tail, c);
}
}
[/sourcecode]

ただし, 呼び元で Handler に対してメッセージをおくっているところはかわらないか.

Chain of responsibility は, chain のリスト構造のなかに, 責務をカプセル化している.

おわりに

デコレータパターンやコンポジットパターンでも感じるが, Gof のデザインパターンは, 関数型で書いたほうが便利なことを がんばって OOP で書いているように思えるのだが.

関数型デザインパターン

ネットで調べたら, やはり同じことを考えている人はいるようだ.

以下の二つは関数型パラダイムでのデザインパターンにもなりえると思う.

  • Decorator Pattern
  • Chain of Responsibility Pattern