2

我很难找到一种优雅的方式来设计一些简单的类来表示 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

谢谢,罗伯。

4

1 回答 1

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)    }
}

这种最新的方法基于 typeclass 概念并且比继承更好地扩展。这个想法是,您不必为层次结构中的每个 T 都拥有一个有效的 HeaderAdder[T],如果您尝试在范围内没有可用的隐式类的类上调用该方法,您将收到编译时错误.

这很棒,因为它可以防止您在层次结构中的某些类变得“脏”时必须实现 addHeader = sys.error("This is not supported") 或重构它以避免它变得“脏”。

管理隐式的最佳方法是将它们置于如下特征中:

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 an reference to an ancient type

于 2013-08-21T09:59:22.940 回答