如何在使用继承时设计不可变模型类

时间:2013-08-21 09:42:50

标签: scala

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

谢谢, 罗布。

1 个答案:

答案 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