【问题标题】:Modular Scala design: how do I avoid a constructor "push-out" boilerplate?模块化 Scala 设计:如何避免构造函数“推出”样板?
【发布时间】:2012-09-04 08:09:14
【问题描述】:

也许一位具有良好风格和优雅感的 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)

}

就目前而言,当我添加两个 Bars 时,我只得到一个 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 的副本,但对于类型检查非常重要),免剪切和粘贴的解决方案会带来好处。

谢谢!

【问题讨论】:

    标签: scala boilerplate


    【解决方案1】:

    我尽量避免继承。所以这里有另一种方法。

    Bar 类与Foo 具有完全相同的构造函数,并且两者都是无状态的。如果你想有几个子类型,只是为了传达任何额外的信息,你可以使用一个泛型参数作为“标签”。例如:

    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)
    

    现在add 是类型安全的。然后,您可以使用类型类来获取 toString 以显示 Kind

    【讨论】:

    • 为了类型安全,我喜欢“参数作为标签”的想法。谢谢!
    【解决方案2】:

    扩展@paradigmatic 的答案,如果您希望能够支持特定于每个Bar 的操作(例如不同的toString),您可以更进一步,将Kind 设为一个类型类。

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

    它和以前一样是类型安全的:

    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)
    

    对于您希望依赖于标签的任何属性,请在 Kind 中抽象地声明它们,并在隐式对象中提供实现。

    【讨论】:

    • 有趣。与在 Foo 中声明 'def name = "Foo"' 方法并让 Bar 和 Biz 覆盖它相比,您的“隐式”方法有什么优势?
    • 这并不是真正的优势,我只是想证明即使在没有继承的环境中,您也可以完全实现这一点,类似于接受的答案中提出的那个。
    【解决方案3】:

    类型参数化的方法有一个限制,它不允许您以自然的方式扩展功能,即继承。因此,另一种方法可能是可覆盖的工厂方法(它只是构造函数的委托):

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多