グーグルドすると、このトピックに対する多くの回答が出てきます。しかし、これらの2つの機能の違いをうまく説明しているようには思えません。もう一度試してみたいのですが、具体的には...
継承ではなく自己型でできること、およびその逆は何ですか?
私には、2つの間に定量化可能な物理的な違いがあるはずです。そうでなければ、それらは名目上異なるだけです。
特性AがBまたは自己型Bを拡張する場合、どちらもBであることは要件であることを示していませんか?違いはどこですか?
グーグルドすると、このトピックに対する多くの回答が出てきます。しかし、これらの2つの機能の違いをうまく説明しているようには思えません。もう一度試してみたいのですが、具体的には...
継承ではなく自己型でできること、およびその逆は何ですか?
私には、2つの間に定量化可能な物理的な違いがあるはずです。そうでなければ、それらは名目上異なるだけです。
特性AがBまたは自己型Bを拡張する場合、どちらもBであることは要件であることを示していませんか?違いはどこですか?
回答:
特性AがBを拡張する場合、Aを混合すると、正確に Bに加えて、Aが追加または拡張するものすべてが得られます。対照的に、トレイトAに明示的にBと型指定された自己参照がある場合、最終的な親クラスもB または子孫タイプのBで混合する必要があります(そして、最初にそれを混合することが重要です)。
それが最も重要な違いです。最初のケースでは、正確なタイプのBは、A点で結晶化し、それを拡張します。2番目の方法では、親クラスの設計者は、親クラスが構成される時点で、使用するBのバージョンを決定します。
別の違いは、AとBが同じ名前のメソッドを提供するところです。AがBを拡張する場合、AのメソッドはBをオーバーライドします。AがBの後に混在している場合、Aの方法が優先されます。
型付き自己参照を使用すると、はるかに自由になります。AとBの間の結合が緩いです。
更新:
これらの違いの利点については明確ではないので...
直接継承を使用する場合は、B + Aである特性Aを作成します。あなたは関係を堅固に設定しました。
型付き自己参照を使用する場合、クラスCで特性Aを使用したい人なら誰でも
Scalaでは、コードブロックをコンストラクターとして直接特性をインスタンス化できるため、これはオプションの制限ではありません。
Aの勝ち方の違いについては、Aが最後に混合されるため、AがBを拡張するのと比較して、これを考慮してください...
一連の特性を組み合わせる場合、メソッドfoo()
が呼び出されるたびに、コンパイラーは最後に組み込まれた特性に移動してを探しfoo()
、次に(見つからない場合)、実装foo()
して使用する特性が見つかるまでシーケンスを左に走査します。それ。Aにはを呼び出すオプションもありますsuper.foo()
。これは、実装が見つかるまでシーケンスを左にトラバースします。
したがって、AがBへの型付き自己参照を持ち、Aの作成者がBが実装foo()
していることをsuper.foo()
知っている場合、A はfoo()
、他に何も提供されなければBが実装することを知って呼び出すことができます。ただし、クラスCの作成者には、implementsの他の特性を削除するオプションがfoo()
あり、Aは代わりにそれを取得します。
繰り返しますが、これはAがBを拡張してBのバージョンを直接呼び出すよりもはるかに強力で制限が少ないですfoo()
。
自己型を拡張する場合と設定する場合の可視性と「デフォルト」実装の違いのいくつかを示すコードがあります。実際の名前の衝突がどのように解決されるかについてすでに説明した部分は示されていませんが、その代わりに何が可能で何が不可能であるかに焦点を当てています。
trait A1 {
self: B =>
def doit {
println(bar)
}
}
trait A2 extends B {
def doit {
println(bar)
}
}
trait B {
def bar = "default bar"
}
trait BX extends B {
override def bar = "bar bx"
}
trait BY extends B {
override def bar = "bar by"
}
object Test extends App {
// object Thing1 extends A1 // FAIL: does not conform to A1 self-type
object Thing1 extends A1 with B
object Thing2 extends A2
object Thing1X extends A1 with BX
object Thing1Y extends A1 with BY
object Thing2X extends A2 with BX
object Thing2Y extends A2 with BY
Thing1.doit // default bar
Thing2.doit // default bar
Thing1X.doit // bar bx
Thing1Y.doit // bar by
Thing2X.doit // bar bx
Thing2Y.doit // bar by
// up-cast
val a1: A1 = Thing1Y
val a2: A2 = Thing2Y
// println(a1.bar) // FAIL: not visible
println(a2.bar) // bar bx
// println(a2.bary) // FAIL: not visible
println(Thing2Y.bary) // 42
}
IMOの重要な違いの1つは、それが(アップキャストの部分に示されているように)それを単に見ているだけのものにA1
必要であることを公開しないことです。の特定の特殊化が使用されていることが実際にわかる唯一のコードは、(のような)構成されたタイプを明示的に知っているコードです。B
A1
B
Think*{X,Y}
もう1つのポイントは、他に何も指定されていない場合A2
は(拡張あり)が実際に使用B
するのに対し、A1
(自己型)はB
、オーバーライドしない限り使用することを示さないため、オブジェクトがインスタンス化されるときに具象Bを明示的に指定する必要があることです。