はじめに

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

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

Chain of Responsibility Pattern

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

要求は,

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

パターン未適用

import java.util.List;
import java.util.LinkedList;

public class ChainOfResponsibilitySample {
  public static void main (String[] args) {
    List<Handler> chain = new LinkedList<Handler>();
    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"); }
}

絶望ポイント

ここがきたない.

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

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

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

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

パターン適用

メリット

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

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

コード

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"); }
}

感動のポイント

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

  public static void main (String[] args) {
    Handler chain = new A (new B (new C (null)));
    chain.handle ('b');
  }

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

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

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

public class ChainOfResponsibilityFinctional {
  public static void main (String[] args) {

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

		handle (chain, 'b');
  }

	static void handle (LinkedList<Handler> chain, char c) {
		Handler head = chain.element ();
		chain.removeFirst ();
		LinkedList<Handler> 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"); }
}

感動のポイント

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

handle (chain, 'b');

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

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

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

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

おわりに

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

関数型デザインパターン

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

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

  • Decorator Pattern
  • Chain of Responsibility Pattern