Skip to content

Instantly share code, notes, and snippets.

@hirofumi
Last active July 11, 2017 00:54
Show Gist options
  • Save hirofumi/5375109 to your computer and use it in GitHub Desktop.
Save hirofumi/5375109 to your computer and use it in GitHub Desktop.
A Description of Cake Pattern

Cake パターン

動機

次のようなコードを考える。

case class User(id: Int, name: String, mail: String)

class DB(host: String, port: Int) {
  def findUserByMail(mail: String): Option[User] = ...
}

class App(db: DB) {
  def showUserName(mail: String) {
    db.findUserByMail(mail) match {
      case None => println("no such user: " + mail)
      case Some(user) => println("name: " + user.name)
    }
  }
}

このとき、どこでどのように AppDB のインスタンスを作るかが問題になる。

嫌な例: コンストラクタから引数をなくす

繋ぐデータベースを変えたくなったときに困る。

class App {
  val db = new DB("example.com", 7777)
  ...
}

object Great {
  val app = new App
  ...
}

object Good {
  val app = new App
  ...
}

嫌な例: その場その場で new する

規模が大きくなると悲しいことになりそう。

object Great {
  val app = new App(new DB("example.com", 7777))
  ...
}

object Good {
  val app = new App(new DB("example.com", 7777))
  ...
}

object TestSuite {
  val app = new App(new DB("test.example.com", 8888))
  ...
}

良い例(?): Cake パターン

次のやり方でインスタンスを組み立てる場所を切り離してまとめることができる。

trait DBComponent {
  val db: DB
  class DB {
    ...
  }
}

trait AppComponent { this: DBComponent =>
  val app: App
  class App {
    ...
  }
}

trait NiceEnvironment extends DBComponent with AppComponent {
  val db = new DB("nice.host", 777)
  val app = new App
}

object Great extends NiceEnvironment {
  ...
}

object Good extends NiceEnvironment {
  ...
}

trait TestEnvironment extends DBComponent with AppComponent {
  val db = new MockDB
  val app = new App
}

object TestSuite extends TestEnvironment {
  ...
}

ちなみに上記の this: DBComponent => は自分型アノテーション (self-type annotation) という。AppComponent を継承する場合は DBComponent も継承していないといけない、という制約を課すのに使っている。

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment