Он преимущественно используется для Injection Dependency , например, в Cake Pattern . Существует великая статья , охватывающая множество различных форм инъекции зависимостей в Scala, включая Pattern Cake. Если вы Google «Cake Pattern и Scala», вы получите много ссылок, включая презентации и видео. На данный момент ссылка на на другой вопрос .
Теперь, что же такое различие между типом self и расширением черты, это просто. Если вы скажете B extends A
, то B
будет а A
. Когда вы используете автотипы, B
требует a A
. Существуют два особых требования, которые создаются с помощью типов:
B
, вы должны смешивать A
. A
. Рассмотрим следующие примеры:
scala> trait User { def name: String }
defined trait User
scala> trait Tweeter {
| user: User =>
| def tweet(msg: String) = println(s"$name: $msg")
| }
defined trait Tweeter
scala> trait Wrong extends Tweeter {
| def noCanDo = name
| }
<console>:9: error: illegal inheritance;
self-type Wrong does not conform to Tweeter's selftype Tweeter with User
trait Wrong extends Tweeter {
^
<console>:10: error: not found: value name
def noCanDo = name
^
Если Tweeter
был подклассом User
, ошибки не было. В приведенном выше коде нам требуется a User
, когда используется Tweeter
, однако User
не был предоставлен Wrong
, поэтому мы получили ошибку. Теперь, используя вышеприведенный код, рассмотрим:
scala> trait DummyUser extends User {
| override def name: String = "foo"
| }
defined trait DummyUser
scala> trait Right extends Tweeter with User {
| val canDo = name
| }
defined trait Right
scala> trait RightAgain extends Tweeter with DummyUser {
| val canDo = name
| }
defined trait RightAgain
С Right
выполняется требование смешивания User
. Однако второе требование, упомянутое выше, не выполняется: бремя реализации User
по-прежнему остается для классов / признаков, которые расширяются Right
.
С RightAgain
оба требования выполнены. Предусмотрены User
и реализация User
.
Для более практичных примеров использования см. ссылки в начале этого ответа! Но, надеюсь, теперь вы его получите.
Тип self позволяет указать, какие типы разрешены для микширования признака. Например, если у вас есть черта с типом self Closeable
, то эта черта знает, что единственные вещи, которым разрешено смешивать ее, должны реализовать интерфейс Closeable
.
Еще одна вещь, о которой не упоминалось: потому что сами типы не являются частью иерархии требуемого класса, они могут быть исключены из соответствия шаблонов, особенно если вы исчерпывающе сопоставляетесь с запечатанной иерархией. Это удобно, если вы хотите моделировать ортогональное поведение, такое как:
sealed trait Person
trait Student extends Person
trait Teacher extends Person
trait Adult { this : Person => } // orthogonal to its condition
val p : Person = new Student {}
p match {
case s : Student => println("a student")
case t : Teacher => println("a teacher")
} // that's it we're exhaustive
в первом случае, субтракт или подкласс класса B можно смешать с любым использованием A. Таким образом, B может быть абстрактным признаком.
Типы типов позволяют вам определять циклические зависимости. Например, вы можете добиться этого:
trait A { self: B => }
trait B { self: A => }
Наследование с использованием extends
не позволяет этого. Попробуйте:
trait A extends B
trait B extends A
error: illegal cyclic reference involving trait A
В книге Odersky посмотрите раздел 33.5 (Создание главы пользовательского интерфейса электронной таблицы), где он упоминает:
В примере электронной таблицы класс Model наследует от Оценщик и, таким образом, получает доступ к методу оценки. Чтобы перейти в другую сторону, класс Evaluator определяет свой тип self как Model:
blockquote>package org.stairwaybook.scells trait Evaluator { this: Model => ...
Надеюсь, что это поможет.
TL; DR сводка других ответов:
class Cow { this: FourStomachs }
позволяет использовать методы, доступные только для жвачных животных, таких как digestGrass
. Однако черты, которые расширяют Корову, не будут иметь таких привилегий. С другой стороны, class Cow extends FourStomachs
будет подвергать digestGrass
любому, кто extends Cow
. Еще одно различие заключается в том, что типы self-types могут указывать типы неклассов. Например,
trait Foo{
this: { def close:Unit} =>
...
}
Тип типа здесь является структурным типом. Эффект состоит в том, чтобы сказать, что все, что смешивается в Foo, должно реализовать возвращаемый модуль метода no-arg «close». Это позволяет использовать безопасные микшины для утиного ввода.
Раздел 2.3 «Аннотации самодельного изображения» оригинальной статьи Скалы Мартина Одерского Масштабируемые абстракции компонентов на самом деле очень хорошо объясняют цель selftype за пределами состава смеси: обеспечить альтернативный способ связывания класса с абстрактным типом.
Пример, приведенный в статье, был похож на следующий, и, похоже, у него нет изящного подкласса:
abstract class Graph {
type Node <: BaseNode;
class BaseNode {
self: Node =>
def connectWith(n: Node): Edge =
new Edge(self, n);
}
class Edge(from: Node, to: Node) {
def source() = from;
def target() = to;
}
}
class LabeledGraph extends Graph {
class Node(label: String) extends BaseNode {
def getLabel: String = label;
def self: Node = this;
}
}
trait A { def x = 1 }
trait B extends A { override def x = super.x * 5 }
trait C1 extends B { override def x = 2 }
trait C2 extends A { this: B => override def x = 2}
// 1.
println((new C1 with B).x) // 2
println((new C2 with B).x) // 10
// 2.
trait X {
type SomeA <: A
trait Inner1 { this: SomeA => } // compiles ok
trait Inner2 extends SomeA {} // doesn't compile
}
Начнем с циклической зависимости.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Однако модульность этого решения не так велика, как может показаться на первый взгляд, потому что вы можете переопределить типы типов следующим образом:
trait A1 extends A {
selfA1: B =>
override def fb = "B's String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A's String" }
val myObj = new A1 with B1
Хотя, если вы переопределите член типа self, вы потеряете доступ к исходному элементу, к которому все еще можно получить доступ через супер, используя наследование. Итак, что действительно наработано с использованием наследования:
trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1
Теперь я не могу утверждать, что понимаю все тонкости шаблона пирога, но мне кажется, что основным методом обеспечения модульности является составление а не наследования или типов.
Версия наследования короче, но основная причина, по которой я предпочитаю наследование по типам self, заключается в том, что мне гораздо сложнее получить правильный порядок инициализации с собственными типами. Однако есть некоторые вещи, которые вы можете делать с самими типами, которые вы не можете сделать с наследованием. Типы типов могут использовать тип, тогда как для наследования требуется черта или класс:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Вы даже можете сделать:
trait TypeBuster
{ this: Int with String => }
Хотя вы никогда не сможете создать экземпляр. Я не вижу абсолютной причины для того, чтобы не унаследовать от типа, но я уверен, что было бы полезно иметь классы и черты конструктора путей, поскольку у нас есть типы / классы конструктора типов. Как, к сожалению,
trait InnerA extends Outer#Inner //Doesn't compile
Мы имеем это:
trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }
Или это:
trait Outer
{ trait Inner }
trait InnerA
{this: Outer#Inner =>}
trait InnerB
{this: Outer#Inner =>}
trait OuterFinal extends Outer
{ val myVal = new InnerA with InnerB with Inner }
. Одна точка, которая должна быть больше сочувствующей, заключается в том, что черты могут расширяет классы. Спасибо Дэвиду Маклверу за это. Вот пример из моего собственного кода:
class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
ScnBase
наследуется от класса кадра Swing , поэтому его можно использовать как тип self, а затем смешать в end (при создании экземпляра). Однако val geomR
необходимо инициализировать, прежде чем использовать его, наследуя черты. Поэтому нам нужен класс для принудительной инициализации geomR
. Класс ScnVista
может затем наследоваться из-за множества ортогональных признаков, которые сами могут быть унаследованы. Использование нескольких параметров типа (generics) предлагает альтернативную форму модульности.
Обновление. Основное отличие состоит в том, что самонастройки могут зависеть от multiple классов (я допускаю, что это бит-код). Например, вы можете иметь
class Person {
//...
def name: String = "...";
}
class Expense {
def cost: Int = 123;
}
trait Employee {
this: Person with Expense =>
// ...
def roomNo: Int;
def officeLabel: String = name + "/" + roomNo;
}
. Это позволяет добавить микс Employee
только к любому подклассу Person
и Expense
. Конечно, это имеет смысл только в том случае, если Expense
распространяется Person
или наоборот. Дело в том, что использование self-types Employee
может быть независимым от иерархии классов, от которых он зависит. Он не заботится о том, что расширяет то, что - если вы переключите иерархию Expense
vs Person
, вам не нужно изменять Employee
.