我很难找到一种优雅的方法来设计一些简单的类来表示Scala中的HTTP消息。
说我有这样的事情:
abstract class HttpMessage(headers: List[String]) {
def addHeader(header: String) = ???
}
class HttpRequest(path: String, headers: List[String])
extends HttpMessage(headers)
new HttpRequest("/", List("foo")).addHeader("bar")
如何使addHeader
方法返回自身的副本并添加新标头? (并保持path
的当前值)
谢谢, 罗布。
答案 0 :(得分:4)
这很烦人,但实现所需模式的解决方案并非无足轻重。
要注意的第一点是,如果要保留子类类型,则需要添加类型参数。如果没有这个,您将无法在HttpMessage
中指定未知的返回类型abstract class HttpMessage(headers: List[String]) {
type X <: HttpMessage
def addHeader(header: String):X
}
然后,您可以在具体子类中实现该方法,您必须在其中指定X的值:
class HttpRequest(path: String, headers: List[String])
extends HttpMessage(headers){
type X = HttpRequest
def addHeader(header: String):HttpRequest = new HttpRequest(path, headers :+header)
}
更好,更具伸缩性的解决方案是将隐含用于此目的。
trait HeaderAdder[T<:HttpMessage]{
def addHeader(httpMessage:T, header:String):T
}
现在您可以在HttpMessage类上定义您的方法,如下所示:
abstract class HttpMessage(headers: List[String]) {
type X <: HttpMessage
def addHeader(header: String)(implicit headerAdder:HeaderAdder[X]):X = headerAdder.add(this,header) }
}
这种最新方法基于类型类概念,并且比继承更好地扩展。我们的想法是,您不必为层次结构中的每个T都有一个有效的HeaderAdder [T],如果您尝试在范围内没有隐式可用的类上调用该方法,则会出现编译时错误
这很棒,因为它可以防止你必须实现addHeader = sys.error(“不支持这个”) 对于层次结构中的某些类,当它变得“脏”或重构它以避免它变得“脏”。
管理隐式的最佳方法是将它们放在如下所示的特征中:
trait HeaderAdders {
implicit val httpRequestHeaderAdder:HeaderAdder[HttpRequest] = new HeaderAdder[HttpRequest] { ... }
implicit val httpRequestHeaderAdder:HeaderAdder[HttpWhat] = new HeaderAdder[HttpWhat] { ... }
}
然后你提供了一个对象,以防用户无法混合它(例如,如果你有通过对象的反射属性调查的框架,你不希望将额外的属性添加到当前实例) (http://www.artima.com/scalazine/articles/selfless_trait_pattern.html)
object HeaderAdders extends HeaderAdders
例如,您可以编写诸如
之类的内容// mixing example
class MyTest extends HeaderAdders // who cares about having two extra value in the object
// import example
import HeaderAdders._
class MyDomainClass // implicits are in scope, but not mixed inside MyDomainClass, so reflection from Hiberante will still work correctly
顺便说一句,这个设计问题与Scala集合相同,唯一的区别是你的HttpMessage是TraversableLike。看看这个问题Calling map on a parallel collection via a reference to an ancestor type