如何在Scala中将流畅的界面与功能样式相结合?

时间:2011-12-30 16:17:28

标签: scala fluent-interface

我一直在JavaJavaScriptScala阅读有关OO'流畅界面'的方法,我喜欢它的外观,但一直在努力寻找如何在Scala中使用更基于类型/功能的方法来协调它。

给出一个非常具体的例子:我编写了一个可以像这样调用的API客户端:

val response = MyTargetApi.get("orders", 24)

来自get()的返回值是名为Tuple3的{​​{1}}类型,正如我package object中所定义的那样:

RestfulResponse

这很好用 - 我真的不想牺牲元组返回值的功能简单性 - 但我想用各种'流畅'方法调用来扩展库,可能是这样的:

// 1. Return code
// 2. Response headers
// 2. Response body (Option)
type RestfulResponse = (Int, List[String], Option[String])

如何将val response = MyTargetApi.get("customers", 55).throwIfError() // Or perhaps: MyTargetApi.get("orders", 24).debugPrint(verbose=true) 返回类型元组(或类似元组)的功能简单性与为我的API添加更多“流畅”功能的功能相结合?

3 个答案:

答案 0 :(得分:7)

您似乎正在处理休息式通信的客户端API。您的get方法似乎是触发实际请求/响应周期的方法。您似乎必须处理此问题:

  • 传输的属性(如凭据,调试级别,错误处理)
  • 提供输入数据(您的 id 类型记录(订单或客户)
  • 对结果做一些事情

我认为对于传输的属性,您可以将其中一些放入MyTargetApi对象的构造函数中,但您也可以创建一个查询对象来存储它们单个查询,可以使用query()方法以流利方式设置:

MyTargetApi.query().debugPrint(verbose=true).throwIfError()

这将返回一些有状态的Query对象,该对象存储日志级别的值,错误处理。为了提供输入的数据,您还可以使用查询对象来设置这些值,而不是返回您的响应返回QueryResult

class Query {
  def debugPrint(verbose: Boolean): this.type = { _verbose = verbose; this }
  def throwIfError(): this.type = { ... }
  def get(tpe: String, id: Int): QueryResult[RestfulResponse] =
    new QueryResult[RestfulResponse] {
       def run(): RestfulResponse = // code to make rest call goes here
    }
}

trait QueryResult[A] { self =>
  def map[B](f: (A) => B): QueryResult[B] = new QueryResult[B] {
    def run(): B = f(self.run())
  }
  def flatMap[B](f: (A) => QueryResult[B]) = new QueryResult[B] {
    def run(): B = f(self.run()).run()
  }
  def run(): A
}

然后最终得到您调用run的结果。所以在一天结束时你可以这样称呼它:

MyTargetApi.query()
  .debugPrint(verbose=true)
  .throwIfError()
  .get("customers", 22)
  .map(resp => resp._3.map(_.length)) // body
  .run()

这应该是一个冗长的请求,会在问题上出错,检索ID为22的客户,保留正文并将其长度设为Option[Int]

这个想法是你可以使用map来定义你还没有的结果的计算。如果我们向其添加flatMap,那么您还可以组合来自两个不同查询的两个计算。

答案 1 :(得分:3)

说实话,我觉得你需要多思索一下,因为这个例子不是显然功能,也不是特别流利。在你的debugPrint方法可能执行I / O而throwIfError抛出异常的意义上,似乎你可能会混淆与 not-idempotent 的流畅性。这是你的意思吗?

如果您指的是有状态构建器是否正常运行,答案是“不是最纯粹的”。但请注意,构建者不必是有状态的。

case class Person(name: String, age: Int)

首先;这可以使用命名参数创建:

Person(name="Oxbow", age=36)

或者,无国籍建设者:

object Person {
  def withName(name: String) 
    = new { def andAge(age: Int) = new Person(name, age) } 
}

嘿presto:

scala> Person withName "Oxbow" andAge 36

关于使用无类型字符串来定义您正在进行的查询;这是一种静态类型语言的糟糕形式。更重要的是,没有必要:

sealed trait Query
case object orders extends Query

def get(query: Query): Result

嘿presto:

api get orders

虽然,我认为这是一个坏主意 - 你不应该有一个单一的方法可以给你带来理论上完全不同类型的结果


总结:我个人认为没有任何理由说流利和功能不能混合,因为功能只是表明缺乏可变状态和强烈偏好幂等函数来执行你的逻辑。

这是给你的一个:

args.map(_.toInt)

args map toInt

我认为第二种更流利。如果你定义:

,这是可能的
val toInt = (_ : String).toInt

那是;如果你定义一个函数。我发现Scala中的函数和流畅性非常好。

答案 2 :(得分:0)

您可以尝试让get()返回一个可能看起来像这样的包装器对象

type RestfulResponse = (Int, List[String], Option[String])

class ResponseWrapper(private rr: RestfulResponse /* and maybe some flags as additional arguments, or something? */) {

    def get : RestfulResponse = rr

    def throwIfError : RestfulResponse = {
        // Throw your exception if you detect an error
        rr    // And return the response if you didn't detect an error
    }

    def debugPrint(verbose: Boolean, /* whatever other parameters you had in mind */) {
        // All of your debugging printing logic
    }

    // Any and all other methods that you want this API response to be able to execute

}

基本上,这允许您将响应放入包含所有这些所需方法的包含中,并且,如果您只想获取包装响应,则可以调用包装器的get()方法。 / p>

当然,这样做的缺点是你需要稍微改变你的API,如果这对你来说太令人担忧了。嗯......你可能可以避免需要更改你的API,实际上,如果你创建了一个从RestfulResponse到ResponseWrapper的隐式转换,反之亦然。这是值得考虑的事情。