How do I make a typesafe builder that doesn't need an explicit build method?

倖福魔咒の 提交于 2019-12-24 16:25:02

问题


I am using a slight abuse of the builder pattern to make a fluent imperative execution chain. What I am after is a way to make it a compile error to forget the execute method at the end. My goal is something like the following

WithServiceA {
 doStuff()
} WithServiceB {
  doStuff()
} withClient client

WithServiceA and WithServiceB can both return values, so if the return value is used it is obvious if the return type is wrong, but if they are used imperatively, the whole object just falls on the floor silently. I want to ensure that forgetting the withClient call is a compile error no matter what context it is used in.

I want to be able to skip blocks if they are unneeded and put them in an arbitrary order so I am looking to replace the nested inner class pattern that I was using previously ala

def onServiceA[A](body: ServiceA => A) = new {   
  def onServiceB[B >: A](body: ServiceB => B) = {b => {
    doStuff()
  }
}

回答1:


It looks like type-safe builder pattern. See this answer.

In your case:

trait TTrue
trait TFalse

class Myclass[TA, TB, TC] private(){
  def withServiceA(x: => Unit)(implicit e: TA =:= TFalse) = {x; new Myclass[TTrue, TB, TC]}
  def withServiceB(x: => Unit)(implicit e: TB =:= TFalse) = {x; new Myclass[TA, TTrue, TC]}
  def withServiceC(x: => Unit)(implicit e: TC =:= TFalse) = {x; new Myclass[TA, TB, TTrue]}
  def withClient(x: => Unit)(implicit e1: TA =:= TTrue, e2: TB =:= TTrue) = x
}

object Myclass{
  def apply() = new Myclass[TFalse, TFalse, TFalse]
}

Usage:

Myclass()
  .withClient(println("withClient"))
//<console>:22: error: Cannot prove that TFalse =:= TTrue.
//                .withClient(println("withClient"))
//                           ^


Myclass()
  .withServiceB(println("with B"))
  .withServiceA(println("with A"))
  .withClient(println("withClient"))
//with B
//with A
//withClient

Myclass()
  .withServiceA(println("with A"))
  .withServiceC(println("with C"))
  .withServiceB(println("with B"))
  .withClient(println("withClient"))
//with A
//with C
//with B
//withClient

Myclass()
  .withServiceC(println("with C"))
  .withServiceB(println("with B"))
  .withServiceA(println("with A"))
  .withServiceC(println("with C2"))
  .withClient(println("withClient"))
//<console>:25: error: Cannot prove that TTrue =:= TFalse.
//                .withServiceC(println("with C2"))
//                             ^

You could provide custom error messages with custom replacements for =:= class.

If you want to be sure that after every Myclass.apply withClient will be called, you could call it manually like this:

sealed class Context private()
object Context {
   def withContext(f: Context => Myclass[TTrue, TTrue, _])(withClient: => Unit) =
     f(new Context).withClient(withClient)
}

object Myclass{
  def apply(c: Context) = new Myclass[TFalse, TFalse, TFalse]
}

Usage:

Context
  .withContext(
    Myclass(_)
      .withServiceA(println("with A"))
      .withServiceC(println("with C"))
      .withServiceB(println("with B"))
  )(println("withClient"))

On ideone.

One can't create Myclass outside of withContext method and withClient will be called at least once.



来源:https://stackoverflow.com/questions/31399669/how-do-i-make-a-typesafe-builder-that-doesnt-need-an-explicit-build-method

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!