我一直在Java,JavaScript和Scala阅读有关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添加更多“流畅”功能的功能相结合?
答案 0 :(得分:7)
您似乎正在处理休息式通信的客户端API。您的get
方法似乎是触发实际请求/响应周期的方法。您似乎必须处理此问题:
我认为对于传输的属性,您可以将其中一些放入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的隐式转换,反之亦然。这是值得考虑的事情。