Вот для чего this.type
:
scala> abstract class Abstract0 {
| def setOption(j: Int): this.type
| }
defined class Abstract0
scala> class Concrete0 extends Abstract0 {
| var i: Int = 0
| def setOption(j: Int) = {i = j; this}
| }
defined class Concrete0
scala> (new Concrete0).setOption(1).setOption(1)
res72: Concrete0 = Concrete0@a50ea1
Как вы можете видеть, setOption возвращает фактический используемый тип, а не Abstract0.Если бы у Concrete0 было setOtherOption
, то (new Concrete0).setOption(1).setOtherOption(...)
сработало бы
ОБНОВЛЕНИЕ: Чтобы ответить на дополнительный вопрос JPP в комментарии (как вернуть новые экземпляры: правильный подход, описанный в вопросе, является правильным (использование абстрактных типов)). Однако создание новых экземпляров должно быть явным для каждого подкласса.
Один из подходов:
abstract class Abstract0 {
type Self <: Abstract0
var i = 0
def copy(i: Int) : Self
def setOption(j: Int): Self = copy(j)
}
class Concrete0(i: Int) extends Abstract0 {
type Self = Concrete0
def copy(i: Int) = new Concrete0(i)
}
Другой подход - следовать шаблону построителя, используемому в библиотеке коллекций Scala.То есть setOption получает неявный параметр компоновщика. Это имеет преимущества в том, что построение нового экземпляра может быть выполнено с помощью большего количества методов, чем просто «копирование», и что можно выполнять сложные сборки. Например, setSpecialOption может указывать, что возвращаемый экземпляр должен бытьSpecialConcrete.
Вот иллюстрация решения:
trait Abstract0Builder[To] {
def setOption(j: Int)
def result: To
}
trait CanBuildAbstract0[From, To] {
def apply(from: From): Abstract0Builder[To]
}
abstract class Abstract0 {
type Self <: Abstract0
def self = this.asInstanceOf[Self]
def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = {
val builder = cbf(self)
builder.setOption(j)
builder.result
}
}
class Concrete0(i: Int) extends Abstract0 {
type Self = Concrete0
}
object Concrete0 {
implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] {
def apply(from: Concrete0) = new Abstract0Builder[Concrete0] {
var i = 0
def setOption(j: Int) = i = j
def result = new Concrete0(i)
}
}
}
object Main {
def main(args: Array[String]) {
val c = new Concrete0(0).setOption(1)
println("c is " + c.getClass)
}
}
ОБНОВЛЕНИЕ 2: Ответ на второй комментарий JPP. В случае нескольких уровней вложенности используйте параметр типа вместо члена типа ипревратить Abstract0 в черту:
trait Abstract0[+Self <: Abstract0[_]] {
// ...
}
class Concrete0 extends Abstract0[Concrete0] {
// ....
}
class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] {
// ....
}