12

也许具有良好风格和优雅感的 Scala 专家可以帮助我找出一种更好的方法来构建以下代码,该代码存在构造函数“推出”问题。

我们从一个简单的基类开始:

class Foo(val i: Int, val d: Double, val s: String) {

  def add(f: Foo) = new Foo(i + f.i, d + f.d, s + f.s)
  override def toString = "Foo(%d,%f,%s)".format(i,d,s)

}

为了在复杂的应用程序中进行类型检查,我需要一个没有任何附加状态的子类:

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}

就目前而言,当我添加两个条时,我只得到一个 Foo:

val x = new Bar(1,2.3,"x")
val y = new Bar(4,5.6,"y")
val xy = x.add(y)

在 REPL 中有以下响应:

x  : Bar = Bar(1,2.300000,x)
y  : Bar = Bar(4,5.600000,y)
xy : Foo = Foo(5,7.900000,xy)

如何以优雅的方式将两个 Bars 添加在一起以形成另一个 Bar(而不是 Foo),而无需复制和粘贴 Foo 的 add 方法,如下所示?

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  // ugly copy-and-paste from Foo:
  def add(b: Bar) = new Bar(i + b.i, d + b.d, s + b.s)
  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}

我有很多这样的 Bars(基本上都是 Foo 的副本,但对于类型检查非常重要),免剪切和粘贴的解决方案将带来好处。

谢谢!

4

3 回答 3

12

I try to avoid inheritance as much as possible. So here is an alternative approach.

Class Bar has exactly the same constructor than Foo and both are stateless. If you want to have several sub-types, just to convey any additional information, you can use a generic parameter as a "label". For instance:

trait Kind
trait Bar extends Kind

class Foo[T<:Kind](val i: Int, val d: Double, val s: String) {
   def add(f: Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
   override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}


scala> val b1 = new Foo[Bar](2,3.0,"hello")
b1: Foo[Bar] = Foo(2,3.000000,hello)

scala> val b2 = new Foo[Bar](3,1.0," world")
b2: Foo[Bar] = Foo(3,1.000000, world)

scala> b1 add b2
res1: Foo[Bar] = Foo(5,4.000000,hello world)

Now add is type safe. You could then use a type class to get the toString to display the Kind.

于 2012-09-04T09:00:21.103 回答
5

Expanding on @paradigmatic's answer, if you want to be able to support operations that are specific to each Bar (e.g. different toString), you can go one step further and make Kind a typeclass.

trait Kind[T] { def name : String }
trait Bar
implicit object BarHasKind extends Kind[Bar] { val name = "Bar" }

class Foo[T : Kind](val i : Int, val d : Double, val s : String) {
  def add(f : Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
  override def toString = implicitly[Kind[T]].name + "(%d,%f,%s)".format(i,d,s)
}

scala> val b1 = new Foo[Bar](2, 3.0, "hello")
b1: Foo[Bar] = Bar(2,3.000000,hello)

trait Biz
implicit object BizHasKind extends Kind[Biz] { val name = "Biz" }

scala> val b2 = new Foo[Biz](1, 1.0, "One")

It is just as type safe as before:

scala> b1 add b2
<console>:16: error: type mismatch;
  found   : Foo[Biz]
  required: Foo[Bar]

scala> b2 add b2
resN: Foo[Biz] = Biz(2,2.000000,OneOne)

For any property that you wish to be dependent on the tag, declare them abstractly in Kind and provide the implementations in the implicit objects.

于 2012-09-04T19:44:07.827 回答
2

The approach with type parametrisation has a limitation in that it doesn't allow you to extend functionality in a natural way which is inheritance. So, an alternative approach could be an override-able factory method (which is simply a delegate to the constructor):

class Foo(val i: Int, val d: Double, val s: String) {

  protected def create(i: Int, d: Double, s: String) = new Foo(i, d, s)

  def add[A <: Foo](f: A) = create(i + f.i, d + f.d, s + f.s)

  override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  protected override def create(i: Int, d: Double, s: String) = new Bar(i, d, s)

  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

  // additional methods...

}

println( new Foo(10, 10.0, "10") add new Bar(10, 10.0, "10") )
println( new Bar(10, 10.0, "10") add new Foo(10, 10.0, "10") )
于 2012-09-07T13:20:19.693 回答