我可以避免在这种情况下使用结构类型吗?

时间:2021-01-09 02:03:41

标签: scala

我有一些代码同时使用第三方库和我自己的库。在我自己的库中,我不想依赖第三方库,所以我希望我的方法之一接受更通用的类型作为参数。不幸的是,我无法将特征扩展或混合到第 3 方类,因为它们是使用工厂方法生成的,并且这些类是 final

我可以通过使用结构化类型来解决这个问题,但我想知道是否有替代方法?如果可能,我不想遍历工厂方法返回的每条记录,并“新建”一个单独类型的实例。

我将其归结为如下场景:

无法更改的第三方库代码

// Class inside library cannot be extended due to it being 'final'
final class SpecificRecord(val values: IndexedSeq[String]) {
  def get(i: Int): String = {
    values(i)
  }

}

// A companion object simply to create some sample data in an iterator
object SpecificRecord{
  def generateSpecificRecords(): Iterator[SpecificRecord] = new Iterator[SpecificRecord] {
    var pointerLocation: Int = 0

    private val state = IndexedSeq(
      IndexedSeq("Row1 Col1", "Row1 Col2", "Row 1 Col3"),
      IndexedSeq("Row2 Col1", "Row2 Col2", "Row 2 Col3")
    )

    override def hasNext: Boolean = {
      if (pointerLocation < state.length) true else false
    }

    override def next(): SpecificRecord = {
      val record = new SpecificRecord(state(pointerLocation))
      pointerLocation += 1
      record
    }
  }
}

如上所示,SpecificRecord 类是 final 类,而 specificRecords val 是一个迭代器,其中包含一堆 SpecificRecord。如果可能,我不想遍历每个 specificRecord 并创建一个新的、更通用的对象。

我可以更改的代码

val specificRecords: Iterator[SpecificRecord] = SpecificRecord.generateSpecificRecords()

type gettable = {
  def get(i: Int): String
}

def printRecord(records: Iterator[gettable]): Unit = {
  for (record <- records) {
    println(record.get(0), record.get(1), record.get(2))
  }
}

printRecord(specificRecords)

这正确打印:

(Row1 Col1,Row1 Col2,Row 1 Col3)
(Row2 Col1,Row2 Col2,Row 2 Col3)

我有一个 printRecord 方法,它并不真正关心传入的是什么类型,只要它有一个像 get(Int): String 这样的方法。这是一个相当不错的解决方案,但我想知道是否可以在没有结构类型的情况下做到这一点?

1 个答案:

答案 0 :(得分:2)

这是类型类的典型用例。

trait Gettable[T] { 
   def get(t: T, i: Int): String
}

object Gettable {
    implicit object SpecificRecordGettable extends Gettable[SpecificRecord] {
        def get(sr: SpecificRecord, i: Int) = sr.get(i)
    }
}

def printRecord[T : Gettable](records: Iterator[T]) = {
  val getter = implicitly[Gettable[T]]
  records.foreach { record => 
    println(getter.get(record, 0), getter.get(record, 1), getter.get(record, 2))
  }
}

这比使用结构化类型的方法更冗长:对于您想要成为 gettable 的每种类型,您必须添加一个实现 get 的隐式对象,但它无需反射即可工作,这是一件好事。

这种方法的另一个优点是它的灵活性:底层类型不必特别具有 get,您可以使用隐式实现任何内容。例如:

   implicit object ArrayGettable extends Gettable[Array[String]] {
      def get(a: Array[String], i: Int) = a(i)
   }

   implicit object ProductGettable extends Gettable[Product] {
      def get(p: Product, i: Int) = p.productIterator.drop(i).next.toString
   }

现在,您的 printRecord 也适用于字符串数组(只要它们至少有三个元素),甚至元组和 case 类。 试试这个:

printRecord[Product](Iterator((1,2, "three"), ("foo", "bar", 5)))

或者这个:

case class Foo(x: String, y: Int, z: Seq[Int])
printRecord[Product](Iterator(Foo("bar", 1, 1::2::Nil), ("foo", "bar", "baz")))

一种类似但稍微不那么冗长的方法是只定义一个隐式的“getter”而不用打扰类型类:

def printRecord[T](records: Iterator[T])(implicit getter: (T,Int) => String) = 
  records.foreach { record => 
    println(getter(record, 0), getter(record, 1), getter(record, 2))
  }

object Getters {
   implicit def getter(sr: SpecificRecord, i: Int) = sr.get(i)
   implicit def getter(a: Array[String], i: Int) = a(i)
   implicit def getter(p: Product, i: Int) = p.productIterator.drop(i).next.toString
}

这在用法上相当等效,不同之处在于类型类允许您定义多个方法,但如果您只需要 get,那么这将为您节省几次按键操作。