2010-11-30 3 views
38

Я не мог найти ответ на этот вопрос в любом другом вопросе. Предположим, что у меня есть абстрактный суперкласс Abstract0 с двумя подклассами Concrete1 и Concrete1. Я хочу иметь возможность определить в Abstract0 что-то вродеКак использовать Scala для этой типизации, абстрактные типы и т. Д. Для реализации типа Self?

def setOption(...): Self = {...} 

где Self будет конкретным подтипом. Это позволит цепочке вызовов SetOption так:

val obj = new Concrete1.setOption(...).setOption(...) 

и до сих пор получить Concrete1 как выведенный типа OBJ.

То, что я не хочу, чтобы определить это:

abstract class Abstract0[T <: Abstract0[T]] 

, потому что это делает его более трудным для клиентов, чтобы справиться с этим типом. Я пробовал различные возможности, включая абстрактный тип:

abstract class Abstract0 { 
    type Self <: Abstract0 
} 

class Concrete1 extends Abstract0 { 
    type Self = Concrete1 
} 

, но тогда невозможно осуществить SetOption, потому что this в Абстракт0 не имеют тип Атман. И использование this: Self => также не работает в Abstract0.

Какие существуют решения для этой проблемы?

+0

Одним из вариантов является определение, например, `защищенные четкости себя = this.asInstanceOf [Само]`, а затем определение функции SetOption (...) = {...; self} `, но это выглядит немного уродливо ... – 2010-11-30 11:30:07

ответ

50

Это то, что 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 = [email protected] 

Как вы можете видеть SetOption возвращает фактический тип, используемый, не Абстракт0. Если Concrete0 был setOtherOption тогда (new Concrete0).setOption(1).setOtherOption(...) будет работать

UPDATE: Чтобы ответить на последующий вопрос 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) 
} 

Еще один должен следовать шаблону строителя, используемый в библиотеке коллекции Скалы То есть, 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) 
    } 
} 

UPDATE 2: Ответ на второй комментарий JPP в. В случае нескольких уровней вложенности, используйте параметр типа вместо элемента типа и сделать Абстракт0 в черту:

trait Abstract0[+Self <: Abstract0[_]] { 
    // ... 
} 

class Concrete0 extends Abstract0[Concrete0] { 
    // .... 
} 

class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] { 
// .... 
} 
+0

Очень приятная особенность языка!Я не помню, чтобы это видели в книге «Программирование в Скала». Но теперь следующий вопрос: это работает только при возвращении. Что делать, если я хочу вернуть другой объект того же конкретного типа, например, для метода клонирования? (Я знаю, что я получаю клон бесплатно как «копировать» в классах классов, но меня все равно интересует вопрос.) – 2010-11-30 11:48:46

4

Это точное использование случай this.type. Это было бы как:

def setOption(...): this.type = { 
    // Do stuff ... 
    this 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^