首页 > 解决方案 > 我可以避免在这种情况下使用结构类型吗?

问题描述

我有一些代码同时使用第三方库和我自己的库。在我自己的库中,我不想依赖第三方库,所以我希望我的方法之一接受更通用的类型作为参数。不幸的是,我无法将特征扩展或混合到第 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类是最终的,而specificRecordsval 是一个迭代器,里面有一堆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. 这是一个相当不错的解决方案,但我想知道是否可以在没有结构类型的情况下做到这一点?

标签: scala

解决方案


这是类型类的典型用例。

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也可以使用字符串数组(只要它们至少包含三个元素),甚至是元组和案例类。尝试这个:

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
}

这在用法上是相当等价的,不同之处在于 type 类允许您潜在地定义多个方法,但如果您只需要get,那么这将为您节省一些击键。


推荐阅读