← 一覧に戻る
振る舞いに関するパターン

Chain of Responsibility

責任のたらい回し

概要

複数のオブジェクトを鎖繋ぎして、そのオブジェクトの鎖を順次渡り歩いて目的のオブジェクトを決定する

マンガでわかる Chain of Responsibility

マンガでわかる Chain of Responsibility #デザインパターン - Qiita

でざぱたんで覚える Chain of Responsibility

ちびキャラは「CORたん」。危険な遺跡で罠のたらい回しに遭うトレジャーハンターで、本書いわく「ザ・たらい回しパターン」。処理対象を受け取った者が「自分で処理できるか」を判断し、できれば処理し、できなければ次へ回す。if-elseifの長い連鎖を分割統治に置き換え、優先度の組み替えも柔軟になる、という要点が罠の連鎖として描かれる。

出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.099〜)

登場人物

  • Handler: 要求を処理するインタフェース
  • 次のHandlerを持つ
  • 要求が飲めないとき次のHandlerに処理を投げる
  • ConcreteHandler(Resolver): 要求処理を実装
  • Client(Sender): ConcreteHandlerに要求を出す

クラス図(&シーケンス図)

W3sDesign_Chain_of_Responsibility_Design_Pattern_UML.jpg (700×240)

このサイトの実装(トラブル解決のたらい回し)での対応関係:

classDiagram
  class Support {
    <<abstract>>
    -name String
    -next Support
    +setNext(next) Support
    +support(trouble)
    #resolve(trouble) bool
    #done(trouble)
    #fail(trouble)
  }
  class NoSupport
  class LimitSupport {
    -limit int
  }
  class SpecialSupport {
    -number int
  }
  class OddSupport
  class Trouble {
    -number int
    +getNumber() int
  }
  Support <|-- NoSupport
  Support <|-- LimitSupport
  Support <|-- SpecialSupport
  Support <|-- OddSupport
  Support o-- Support : next
  Support ..> Trouble

やり方

  • ConcreteHandlerに解決条件・解決できないとき次に渡すHanlder、解決処理を持たせる
  • Clientは最初のConcreteHandlerに対して一度だけ要求を投げる

メリット(用途)

  • 「誰かが解決してくれる」
  • 要求者であるClientが解決者を知っている(解決者に依存する)のはよくないが、たらい回しにすることで依存を解消できる
Java
OddSupport.java
public class OddSupport extends Support {
    public OddSupport(String name) {                // コンストラクタ
        super(name);
    }
    protected boolean resolve(Trouble trouble) {    // 解決用メソッド
        if (trouble.getNumber() % 2 == 1) {
            return true;
        } else {
            return false;
        }
    }
}
Support.java
public abstract class Support {
    private String name;                    // このトラブル解決者の名前
    private Support next;                 // たらい回しの先
    public Support(String name) {           // トラブル解決者の生成
        this.name = name;
    }
    public Support setNext(Support next) {  // たらい回しの先を設定
        this.next = next;
        return next;
    }
    public void support(Trouble trouble) {  // トラブル解決の手順
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
    }
    public String toString() {              // 文字列表現
        return "[" + name + "]";
    }
    protected abstract boolean resolve(Trouble trouble); // 解決用メソッド
    protected void done(Trouble trouble) {  // 解決
        System.out.println(trouble + " is resolved by " + this + ".");
    }
    protected void fail(Trouble trouble) {  // 未解決
        System.out.println(trouble + " cannot be resolved.");
    }
}
LimitSupport.java
public class LimitSupport extends Support {
    private int limit;                              // この番号未満なら解決できる
    public LimitSupport(String name, int limit) {   // コンストラクタ
        super(name);
        this.limit = limit;
    }
    protected boolean resolve(Trouble trouble) {         // 解決用メソッド
        if (trouble.getNumber() < limit) {
            return true;
        } else {
            return false;
        }
    }
}
NoSupport.java
public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }
    protected boolean resolve(Trouble trouble) {     // 解決用メソッド
        return false; // 自分は何も処理しない
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Support alice   = new NoSupport("Alice");
        Support bob     = new LimitSupport("Bob", 100);
        Support charlie = new SpecialSupport("Charlie", 429);
        Support diana   = new LimitSupport("Diana", 200);
        Support elmo    = new OddSupport("Elmo");
        Support fred    = new LimitSupport("Fred", 300);
        // 連鎖の形成
        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
        // さまざまなトラブル発生
        for (int i = 0; i < 500; i += 33) {
            alice.support(new Trouble(i));
        }
    }
}
Trouble.java
public class Trouble {
    private int number;             // トラブル番号
    public Trouble(int number) {    // トラブルの生成
        this.number = number;
    }
    public int getNumber() {        // トラブル番号を得る
        return number;
    }
    public String toString() {      // トラブルの文字列表現
        return "[Trouble " + number + "]";
    }
}
SpecialSupport.java
public class SpecialSupport extends Support {
    private int number;                                 // この番号だけ解決できる
    public SpecialSupport(String name, int number) {    // コンストラクタ
        super(name);
        this.number = number;
    }
    protected boolean resolve(Trouble trouble) {     // 解決用メソッド
        if (trouble.getNumber() == number) {
            return true;
        } else {
            return false;
        }
    }
}
Go

Supportはinterfaceで表現。Javaのprotectedな共通実装(setNext(), support(), toString(), done(), fail())のうち、状態(name/next)と一部メソッドは埋め込み(baseSupport)で共有できるが、「サブクラスのresolve()を呼び戻す」テンプレートメソッド本体(support())だけはGoの埋め込みでは表現できない(仮想呼び出し非対応)ため、FactoryMethod版のCreate()と同じ流儀でHandleTrouble(s Support, trouble Trouble)という自由関数に外出ししている。

実行: go run ./GoF/patterns/ChainOfResponsibility/go

$ go run ./GoF/patterns/ChainOfResponsibility/go
trouble.go
package main

import "fmt"

// ---- trouble 層(Java版の Trouble.java に相当)----

// Trouble は「相談窓口」に持ち込まれる問題を表す値。
// 中身は番号だけの単純な値なので、Strategy/go の Hand と同様に値型で持つ。
type Trouble struct {
	number int
}

// NewTrouble はコンストラクタ相当。
func NewTrouble(number int) Trouble {
	return Trouble{number: number}
}

// Number はトラブル番号を得る。
func (t Trouble) Number() int {
	return t.number
}

// String は fmt.Stringer を満たす(Java版 toString() 相当)。
func (t Trouble) String() string {
	return fmt.Sprintf("[Trouble %d]", t.number)
}
support.go
package main

import "fmt"

// ---- support 層(Java版の Support.java に相当)----
//
// Java版 Support は「name/nextという状態」と「setNext/support/toString/done/failという
// 共通の具象メソッド」を持つ抽象クラスで、resolve()だけをサブクラスに実装させる
// テンプレートメソッドパターンになっている。
//
// Goには継承が無いので、共通状態は baseSupport にまとめて各具象窓口へ埋め込み
// (embedding)で共有し、「サブクラスの resolve() を呼び出す」テンプレートメソッド本体は
// FactoryMethod/go の Create(c Creator, ...) と同じ流儀で、Support を引数に取る自由関数
// HandleTrouble に外出しする。埋め込みでは baseSupport 側から具象型の Resolve を
// 呼び戻すことができない(Goの埋め込みは仮想呼び出しをサポートしない)ため、この形が
// 一番自然。

// Support は「相談窓口」を表す抽象。NoSupport/LimitSupport/OddSupport/SpecialSupportが
// これを実装するConcreteHandler。
type Support interface {
	fmt.Stringer
	Next() Support                // たらい回しの先を得る(テンプレートメソッドHandleTroubleが使う)
	SetNext(next Support) Support // たらい回しの先を設定
	Resolve(trouble Trouble) bool // 解決用メソッド(各窓口が実装する“抽象”部分)
}

// baseSupport は全ての具象窓口が共有する状態(name/next)と、Resolve以外の共通実装。
// Java版でいう Support の非abstractな部分に相当。
type baseSupport struct {
	name string  // このトラブル解決者の名前
	next Support // たらい回しの先
}

// SetNext はたらい回しの先を設定する。Java版と同様、渡した引数(next)をそのまま返すので
// alice.SetNext(bob).SetNext(charlie)のように連結できる。
func (b *baseSupport) SetNext(next Support) Support {
	b.next = next
	return next
}

// Next はたらい回しの先を得る。
func (b *baseSupport) Next() Support {
	return b.next
}

// String は fmt.Stringer を満たす(Java版 toString() 相当)。
func (b *baseSupport) String() string {
	return "[" + b.name + "]"
}

// HandleTrouble はJava版 Support.support(Trouble) 相当のテンプレートメソッド。
// 「resolveできたらdone、できなければ次へたらい回し、次がなければfail」という
// 手順そのものは全窓口で共通で、resolveの中身だけが窓口ごとに違う。
func HandleTrouble(s Support, trouble Trouble) {
	if s.Resolve(trouble) {
		done(s, trouble)
	} else if next := s.Next(); next != nil {
		HandleTrouble(next, trouble)
	} else {
		fail(s, trouble)
	}
}

// done と fail はJava版 Support.done()/fail()(protected、未オーバーライド)相当。
func done(s Support, trouble Trouble) {
	fmt.Printf("%s is resolved by %s.\n", trouble, s)
}

func fail(s Support, trouble Trouble) {
	fmt.Printf("%s cannot be resolved.\n", trouble)
}
no_support.go
package main

// NoSupport はConcreteHandlerの一つ。何も解決しない(Java版 NoSupport.java 相当)。
type NoSupport struct {
	baseSupport
}

// NewNoSupport はコンストラクタ相当。
func NewNoSupport(name string) *NoSupport {
	return &NoSupport{baseSupport{name: name}}
}

// Resolve は解決用メソッド。常に自分では処理しない。
func (s *NoSupport) Resolve(trouble Trouble) bool {
	return false
}
limit_support.go
package main

// LimitSupport はConcreteHandlerの一つ。番号がlimit未満なら解決できる
// (Java版 LimitSupport.java 相当)。
type LimitSupport struct {
	baseSupport
	limit int // この番号未満なら解決できる
}

// NewLimitSupport はコンストラクタ相当。
func NewLimitSupport(name string, limit int) *LimitSupport {
	return &LimitSupport{baseSupport{name: name}, limit}
}

// Resolve は解決用メソッド。
func (s *LimitSupport) Resolve(trouble Trouble) bool {
	return trouble.Number() < s.limit
}
odd_support.go
package main

// OddSupport はConcreteHandlerの一つ。奇数番号だけ解決できる
// (Java版 OddSupport.java 相当)。
type OddSupport struct {
	baseSupport
}

// NewOddSupport はコンストラクタ相当。
func NewOddSupport(name string) *OddSupport {
	return &OddSupport{baseSupport{name: name}}
}

// Resolve は解決用メソッド。
func (s *OddSupport) Resolve(trouble Trouble) bool {
	return trouble.Number()%2 == 1
}
special_support.go
package main

// SpecialSupport はConcreteHandlerの一つ。特定の番号だけ解決できる
// (Java版 SpecialSupport.java 相当)。
type SpecialSupport struct {
	baseSupport
	number int // この番号だけ解決できる
}

// NewSpecialSupport はコンストラクタ相当。
func NewSpecialSupport(name string, number int) *SpecialSupport {
	return &SpecialSupport{baseSupport{name: name}, number}
}

// Resolve は解決用メソッド。
func (s *SpecialSupport) Resolve(trouble Trouble) bool {
	return trouble.Number() == s.number
}
main.go
package main

// 実行: go run ./GoF/patterns/ChainOfResponsibility/go
//
// Java版Main.javaと同じ6窓口の連鎖(Alice→Bob→Charlie→Diana→Elmo→Fred)を組み立て、
// 0,33,66,...,495のトラブルを次々に持ち込んで、誰がどう解決するかを観察する。
func main() {
	alice := NewNoSupport("Alice")
	bob := NewLimitSupport("Bob", 100)
	charlie := NewSpecialSupport("Charlie", 429)
	diana := NewLimitSupport("Diana", 200)
	elmo := NewOddSupport("Elmo")
	fred := NewLimitSupport("Fred", 300)
	// 連鎖の形成。SetNextは渡した引数(next)をそのまま返すので連結できる。
	alice.SetNext(bob).SetNext(charlie).SetNext(diana).SetNext(elmo).SetNext(fred)

	// さまざまなトラブル発生
	for i := 0; i < 500; i += 33 {
		HandleTrouble(alice, NewTrouble(i))
	}
}
PHP
index.php
<?php
ini_set("display_errors", "1");
error_reporting(E_ALL);

/**
 * 
 * Chain Of Responsibility
 * 「責任の連鎖」
 * 「優先度付きの処理フローを定義」
 * ザ・たらい回しパターン
 * オブジェクトは受け取ったものを
 * 自分で処理できるなら処理
 * 処理できないなら「次の人へお願い」
 * 
 */


/**
 * 「遺跡の部屋の名前を判断する」多重分岐を
 * ケースを分割統治して解決する
 * 
 */


/**
 * ダンジョンクラス
 * レベルが高いダンジョンほど攻略難
 */


class Dungeon
{
  private $level = null;
  public function __construct($level)
  {
    $this->level = $level;
  }

  public function getLevel()
  {
    return $this->level;
  }
}


// /**
//  * 冒険者抽象クラス
//  * 自分ではダンジョンをクリアできないときのために次の冒険者を保持
//  * 
//  */

abstract class Adventurer
{
  protected $level;
  private $name;
  private $next;


  /**
   * 冒険者名を保持
   *
   * @param string $name
   */
  public function __construct($name)
  {
    $this->name = $name;
  }

  /**
   * 自分ではクリアできない
   * 次に任せる人をセットする
   * @param $next 冒険者を継承した何か
   */

  public function setNext(Adventurer $next)
  {
    $this->next = $next;
    return $this->next;
  }

  /**
   * メイン処理
   * 自分がダンジョンをクリアできるのならclear
   * できないなら次の人に任せる
   * @param $dungeon
   */
  public function challenge($dungeon)
  {
    if ($this->canClear($dungeon)) {
      $this->clear($dungeon);
    } elseif ($this->next != null) {
      print "$this は クリアできないので$this->next にお任せします. <br> ";
      $this->next->challenge($dungeon);
    } else {
      $this->fail($dungeon);
    }
  }


  public function __toString()
  {
    return $this->name;
  }
  /**
   * ダンジョンをクリアできるかどうかの判定はサブクラスに任せる
   *
   * @param [type] $arg
   * @return void
   */
  public abstract function canClear(Dungeon $dungeon);

  /**
   * ダンジョンクリア時に呼ばれる
   */
  protected function clear(Dungeon $dungeon)
  {
    print $this . "がレベル" . $dungeon->getLevel() . "ダンジョンを攻略 <br>";
  }

  /**
   * たらい回しにされた結果、誰も処理できないときに呼ばれる
   *
   * @param [type] $arg
   * @return void
   */
  protected function fail($dungeon)
  {
    print "誰も攻略できない <br>";
  }
}


/**
 * 初心者の冒険者クラス
 */



class Beginner extends Adventurer
{

  public function __construct($name, $level)
  {
    parent::__construct($name);
    // $this->name = $name;
    $this->level  = $level;
  }

  public function canClear(Dungeon $dungeon)
  {
    return (20 > $dungeon->getLevel() && $dungeon->getLevel() < $this->level);
  }
}


/**
 * 中級者
 */
class Intermediate extends Adventurer
{

  public function __construct($name, $level)
  {
    parent::__construct($name);
    $this->level  = $level;
  }

  /**
   * レベル40未満のダンジョン
   * かつ自身のレベルより低いダンジョンならクリアできる
   */

  public function canClear(Dungeon $dungeon)
  {
    return 40 > $dungeon->getLevel() && $dungeon->getLevel() < $this->level;
  }
}


/**
 * 上級者。
 * 自分のレベルを持たない。カンストしたとでも思えば良い
 */
class Expert extends Adventurer
{

  public function __construct($name)
  {
    parent::__construct($name);
  }

  /**
   * レベル100未満のダンジョン
   * なら問答無用でクリアできる
   */

  public function canClear(Dungeon $dungeon)
  {
    return 100 > $dungeon->getLevel();
  }
}


// main

$beginner = new Beginner("初心者", 5);
$intermediate = new Intermediate("中級者", 24);
$expert = new Expert("上級者");


// 初心者、中級者、上級者の順に処理を任せる;
$beginner->setNext($intermediate)->setNext($expert);

// 挑ませる

$beginner->challenge(new Dungeon(15));
$beginner->challenge(new Dungeon(4));
$beginner->challenge(new Dungeon(60));
$beginner->challenge(new Dungeon(100));



// 動的に連鎖を変えられる
$beginerB = new Beginner("初心者B", 5);
$interB = new Intermediate("中級者B", 24);
$expertB = new Expert("上級者B");

$expertB->setNext($beginerB)->setNext($interB);
$expertB->challenge(new Dungeon(15));
$expertB->challenge(new Dungeon(4));
$expertB->challenge(new Dungeon(60));
$expertB->challenge(new Dungeon(100));
TypeScript

abstract class SupportsetNext()/support()(テンプレートメソッド)/toString()/done()/fail()を実装し、resolve()だけを各具象窓口の抽象メソッドにする、というJava版そのままの設計。

実行: npx tsx GoF/patterns/ChainOfResponsibility/typescript/main.ts

$ npx tsx GoF/patterns/ChainOfResponsibility/typescript/main.ts
trouble.ts
// Chain of Responsibility パターン: トラブル層 (Java版の Trouble.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

// Trouble: 「相談窓口」に持ち込まれる問題を表す値。
export class Trouble {
  constructor(private readonly number: number) {}

  getNumber(): number {
    return this.number;
  }

  toString(): string {
    return `[Trouble ${this.number}]`;
  }
}
support.ts
// Chain of Responsibility パターン: 窓口層 (Java版の Support.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版のSupportは抽象クラスで、name/nextという状態と、setNext/support/toString/
// done/failという共通の具象メソッドを持ち、resolve()だけをサブクラスに実装させる
// テンプレートメソッドパターンになっている。TypeScriptにもabstractクラスがあるので、
// Composite版TS実装と同じ方針でJavaのクラス階層をほぼそのまま書ける
// (Go版のようにテンプレートメソッド本体を自由関数へ外出しする必要がない)。

import { Trouble } from "./trouble";

export abstract class Support {
  private next: Support | null = null; // たらい回しの先

  constructor(private readonly name: string) {} // このトラブル解決者の名前

  // たらい回しの先を設定。Java版と同様、渡した引数(next)をそのまま返すので
  // alice.setNext(bob).setNext(charlie)のように連結できる。
  setNext(next: Support): Support {
    this.next = next;
    return next;
  }

  // トラブル解決の手順(テンプレートメソッド)。
  support(trouble: Trouble): void {
    if (this.resolve(trouble)) {
      this.done(trouble);
    } else if (this.next !== null) {
      this.next.support(trouble);
    } else {
      this.fail(trouble);
    }
  }

  // Java版 toString() 相当。
  toString(): string {
    return `[${this.name}]`;
  }

  // 解決用メソッド。各窓口(サブクラス)が実装する“抽象”部分。
  protected abstract resolve(trouble: Trouble): boolean;

  // 解決(未オーバーライド)。
  protected done(trouble: Trouble): void {
    console.log(`${trouble} is resolved by ${this}.`);
  }

  // 未解決(未オーバーライド)。
  protected fail(trouble: Trouble): void {
    console.log(`${trouble} cannot be resolved.`);
  }
}
no_support.ts
// Chain of Responsibility パターン: 何も解決しない窓口 (Java版の NoSupport.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Support } from "./support";
import { Trouble } from "./trouble";

export class NoSupport extends Support {
  constructor(name: string) {
    super(name);
  }

  protected resolve(_trouble: Trouble): boolean {
    return false; // 自分は何も処理しない
  }
}
limit_support.ts
// Chain of Responsibility パターン: 番号がlimit未満なら解決できる窓口
// (Java版の LimitSupport.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Support } from "./support";
import { Trouble } from "./trouble";

export class LimitSupport extends Support {
  constructor(
    name: string,
    private readonly limit: number, // この番号未満なら解決できる
  ) {
    super(name);
  }

  protected resolve(trouble: Trouble): boolean {
    return trouble.getNumber() < this.limit;
  }
}
odd_support.ts
// Chain of Responsibility パターン: 奇数番号だけ解決できる窓口
// (Java版の OddSupport.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Support } from "./support";
import { Trouble } from "./trouble";

export class OddSupport extends Support {
  constructor(name: string) {
    super(name);
  }

  protected resolve(trouble: Trouble): boolean {
    return trouble.getNumber() % 2 === 1;
  }
}
special_support.ts
// Chain of Responsibility パターン: 特定の番号だけ解決できる窓口
// (Java版の SpecialSupport.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Support } from "./support";
import { Trouble } from "./trouble";

export class SpecialSupport extends Support {
  constructor(
    name: string,
    private readonly number: number, // この番号だけ解決できる
  ) {
    super(name);
  }

  protected resolve(trouble: Trouble): boolean {
    return trouble.getNumber() === this.number;
  }
}
main.ts
// Chain of Responsibility パターン: たらい回しの窓口 (Java版Main.javaと同じお題)
//
// 実行: npx tsx GoF/patterns/ChainOfResponsibility/typescript/main.ts
//
// Java版と同じ6窓口の連鎖(Alice→Bob→Charlie→Diana→Elmo→Fred)を組み立て、
// 0,33,66,...,495のトラブルを次々に持ち込んで、誰がどう解決するかを観察する。

import { LimitSupport } from "./limit_support";
import { NoSupport } from "./no_support";
import { OddSupport } from "./odd_support";
import { SpecialSupport } from "./special_support";
import { Trouble } from "./trouble";

function main(): void {
  const alice = new NoSupport("Alice");
  const bob = new LimitSupport("Bob", 100);
  const charlie = new SpecialSupport("Charlie", 429);
  const diana = new LimitSupport("Diana", 200);
  const elmo = new OddSupport("Elmo");
  const fred = new LimitSupport("Fred", 300);
  // 連鎖の形成
  alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);

  // さまざまなトラブル発生
  for (let i = 0; i < 500; i += 33) {
    alice.support(new Trouble(i));
  }
}

main();
Python

Support(ABC)にJava版と同じ形で共通実装(set_next(), support(), __str__(), _done(), _fail())を持たせ、_resolve()だけを抽象メソッドにする。

実行: python3 GoF/patterns/ChainOfResponsibility/python/main.py

$ python3 GoF/patterns/ChainOfResponsibility/python/main.py
trouble.py
"""トラブル層 (Java版の Trouble.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations


class Trouble:
    """Trouble: 「相談窓口」に持ち込まれる問題を表す値。"""

    def __init__(self, number: int) -> None:
        self._number = number

    def get_number(self) -> int:
        return self._number

    def __str__(self) -> str:
        return f"[Trouble {self._number}]"
support.py
"""窓口層 (Java版の Support.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

Java版のSupportは抽象クラスで、name/nextという状態と、set_next/support/__str__/
done/failという共通の具象メソッドを持ち、resolve()だけをサブクラスに実装させる
テンプレートメソッドパターンになっている。Composite版Python実装と同じ方針で
抽象基底クラス(ABC, abcモジュール)を使い、Javaのクラス階層をほぼそのまま書ける。
"""

from __future__ import annotations

from abc import ABC, abstractmethod

from trouble import Trouble


class Support(ABC):
    def __init__(self, name: str) -> None:
        self._name = name  # このトラブル解決者の名前
        self._next: Support | None = None  # たらい回しの先

    def set_next(self, next_: Support) -> Support:
        """たらい回しの先を設定。Java版と同様、引数(next)をそのまま返すので
        alice.set_next(bob).set_next(charlie)のように連結できる。
        """
        self._next = next_
        return next_

    def support(self, trouble: Trouble) -> None:
        """トラブル解決の手順(テンプレートメソッド)。"""
        if self._resolve(trouble):
            self._done(trouble)
        elif self._next is not None:
            self._next.support(trouble)
        else:
            self._fail(trouble)

    def __str__(self) -> str:
        """Java版 toString() 相当。"""
        return f"[{self._name}]"

    @abstractmethod
    def _resolve(self, trouble: Trouble) -> bool:
        """解決用メソッド。各窓口(サブクラス)が実装する"抽象"部分。"""
        ...

    def _done(self, trouble: Trouble) -> None:
        """解決(未オーバーライド)。"""
        print(f"{trouble} is resolved by {self}.")

    def _fail(self, trouble: Trouble) -> None:
        """未解決(未オーバーライド)。"""
        print(f"{trouble} cannot be resolved.")
no_support.py
"""何も解決しない窓口 (Java版の NoSupport.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from support import Support
from trouble import Trouble


class NoSupport(Support):
    def __init__(self, name: str) -> None:
        super().__init__(name)

    def _resolve(self, trouble: Trouble) -> bool:
        return False  # 自分は何も処理しない
limit_support.py
"""番号がlimit未満なら解決できる窓口 (Java版の LimitSupport.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from support import Support
from trouble import Trouble


class LimitSupport(Support):
    def __init__(self, name: str, limit: int) -> None:
        super().__init__(name)
        self._limit = limit  # この番号未満なら解決できる

    def _resolve(self, trouble: Trouble) -> bool:
        return trouble.get_number() < self._limit
odd_support.py
"""奇数番号だけ解決できる窓口 (Java版の OddSupport.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from support import Support
from trouble import Trouble


class OddSupport(Support):
    def __init__(self, name: str) -> None:
        super().__init__(name)

    def _resolve(self, trouble: Trouble) -> bool:
        return trouble.get_number() % 2 == 1
special_support.py
"""特定の番号だけ解決できる窓口 (Java版の SpecialSupport.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from support import Support
from trouble import Trouble


class SpecialSupport(Support):
    def __init__(self, name: str, number: int) -> None:
        super().__init__(name)
        self._number = number  # この番号だけ解決できる

    def _resolve(self, trouble: Trouble) -> bool:
        return trouble.get_number() == self._number
main.py
"""Chain of Responsibility パターン: たらい回しの窓口 (Java版Main.javaと同じお題)

実行: python3 main.py
      (もしくはリポジトリルートから python3 GoF/patterns/ChainOfResponsibility/python/main.py)

Java版と同じ6窓口の連鎖(Alice→Bob→Charlie→Diana→Elmo→Fred)を組み立て、
0,33,66,...,495のトラブルを次々に持ち込んで、誰がどう解決するかを観察する。
"""

from __future__ import annotations

from limit_support import LimitSupport
from no_support import NoSupport
from odd_support import OddSupport
from special_support import SpecialSupport
from trouble import Trouble


def main() -> None:
    alice = NoSupport("Alice")
    bob = LimitSupport("Bob", 100)
    charlie = SpecialSupport("Charlie", 429)
    diana = LimitSupport("Diana", 200)
    elmo = OddSupport("Elmo")
    fred = LimitSupport("Fred", 300)
    # 連鎖の形成
    alice.set_next(bob).set_next(charlie).set_next(diana).set_next(elmo).set_next(fred)

    # さまざまなトラブル発生
    for i in range(0, 500, 33):
        alice.support(Trouble(i))


if __name__ == "__main__":
    main()