Chain of ResponsibilityパターンのScalaによる実装サンプル


JJUGナイトセミナーで紹介されていた(私は出席してないので伝聞) Chain of Responsibility をJavaのラムダとSteram APIで実装する サンプル を見て思ったのは、Javaだとやっぱり記述が冗長になってしまうなーという点。

ということで、上記サンプルと同じことをScalaでやってみた。

まずはFileクラスの定義:

File.scala
sealed abstract class File(val content: String)
case class Text(c: String) extends File(c)
case class Presentation(c: String) extends File(c)
case class Audio(c: String) extends File(c)
case class Video(c: String) extends File(c)

CoRを構成する個々のハンドラを表すトレイト:
--> メソッドは、自分が処理できない場合に次のハンドラに処理を委譲する、合成された Handler を生成するもの。

Handler.scala
trait Handler {
  def handle(file: File): Option[String]

  def -->(next: Handler): Handler = file => {
    val result = this.handle(file)
    result match {
      case Some(s) => Some(s)
      case None => next.handle(file)
    }
  }
}

使用例:
CoRを h1 --> h2 --> h3 --> h4 と視覚的に表現できるのがうれしい。

ChainOfRespTest.scala
import org.scalatest._
import org.scalatest.Matchers._

class ChainOfRespTest extends FunSuite with Matchers {

    test("Chain of Responsibility works fine") {
      val h1: Handler = _ match {
        case Text(c) => Some("Text file: " + c)
        case _ => None
      }
      val h2: Handler = _ match {
        case Presentation(c) => Some("Presentation file: " + c)
        case _ => None
      }
      val h3: Handler = _ match {
        case Audio(c) => Some("Audio file: " + c)
        case _ => None
      }
      val h4: Handler = _ match {
        case Video(c) => Some("Video file: " + c)
        case _ => None
      }
      val result = h1 --> h2 --> h3 --> h4 handle Audio("Blackout")

      result should be (Some("Audio file: Blackout"))
    }
}