Tour of Scala

Композиция классов с трейтами

Language

Примеси (Mixin) - это трейты, которые используются для создания класса.

abstract class A {
  val message: String
}
class B extends A {
  val message = "I'm an instance of class B"
}
trait C extends A {
  def loudMessage = message.toUpperCase()
}
class D extends B with C

val d = new D
println(d.message)  // I'm an instance of class B
println(d.loudMessage)  // I'M AN INSTANCE OF CLASS B

У класса D есть суперкласс B и трейт C. Классы могут иметь только один суперкласс, но много трейтов (используя ключевое слово extends и with соответственно). Трейты и суперкласс могут иметь один и тот же супертип.

abstract class A:
  val message: String
class B extends A:
  val message = "I'm an instance of class B"
trait C extends A:
  def loudMessage = message.toUpperCase()
class D extends B, C

val d = D()
println(d.message)  // I'm an instance of class B
println(d.loudMessage)  // I'M AN INSTANCE OF CLASS B

У класса D есть суперкласс B и трейт C. Классы могут иметь только один суперкласс, но много трейтов (используя ключевое слово extends и разделитель , соответственно). Трейты и суперкласс могут иметь один и тот же супертип.

Теперь давайте рассмотрим более интересный пример, начиная с абстрактного класса:

abstract class AbsIterator {
  type T
  def hasNext: Boolean
  def next(): T
}
abstract class AbsIterator:
  type T
  def hasNext: Boolean
  def next(): T

Класс имеет абстрактный тип T и методы стандартного итератора.

Далее создаем конкретную реализацию класса (все абстрактные члены T, hasNext, и next должны быть реализованы):

class StringIterator(s: String) extends AbsIterator {
  type T = Char
  private var i = 0
  def hasNext = i < s.length
  def next() = {
    val ch = s charAt i
    i += 1
    ch
  }
}
class StringIterator(s: String) extends AbsIterator:
  type T = Char
  private var i = 0
  def hasNext = i < s.length
  def next() =
    val ch = s charAt i
    i += 1
    ch

StringIterator принимает String и может быть использован для обхода по строке (например, чтоб проверить содержит ли строка определенный символ).

Теперь давайте создадим трейт который тоже наследуется от AbsIterator.

trait RichIterator extends AbsIterator {
  def foreach(f: T => Unit): Unit = while (hasNext) f(next())
}

У этого трейта реализован метод foreach, который постоянно вызывает переданную ему функцию f: T => Unit на каждом новом элементе (next()) до тех пор пока в итераторе содержатся элементы (while (hasNext)). Поскольку RichIterator - это трейт, ему не нужно реализовывать элементы абстрактного класса AbsIterator.

trait RichIterator extends AbsIterator:
  def foreach(f: T => Unit): Unit = while hasNext do f(next())

У этого трейта реализован метод foreach, который постоянно вызывает переданную ему функцию f: T => Unit на каждом новом элементе (next()) до тех пор пока в итераторе содержатся элементы (while hasNext). Поскольку RichIterator - это трейт, ему не нужно реализовывать элементы абстрактного класса AbsIterator.

Мы бы хотели объединить функциональность StringIterator и RichIterator в один класс.

class RichStringIter extends StringIterator("Scala") with RichIterator
val richStringIter = new RichStringIter
richStringIter.foreach(println)
class RichStringIter extends StringIterator("Scala"), RichIterator
val richStringIter = RichStringIter()
richStringIter.foreach(println)

Новый класс RichStringIter включает StringIterator как суперкласс и RichIterator как трейт.

Используя только одиночное наследование мы бы не смогли добиться того же уровня гибкости.

Contributors to this page: