Scalaでの自己型と特性継承の違いは何ですか?


9

グーグルドすると、このトピックに対する多くの回答が出てきます。しかし、これらの2つの機能の違いをうまく説明しているようには思えません。もう一度試してみたいのですが、具体的には...

継承ではなく自己型でできること、およびその逆は何ですか?

私には、2つの間に定量化可能な物理的な違いがあるはずです。そうでなければ、それらは名目上異なるだけです。

特性AがBまたは自己型Bを拡張する場合、どちらもBであることは要件であることを示していませんか?違いはどこですか?


バウンティに設定した条件に注意します。1つには、これがすべてソフトウェアであることを前提として、「物理的な」違いを定義します。それを超えて、ミックスインで作成した複合オブジェクトの場合、関数を継承で近似したものを作成できます(可視メソッドで関数を純粋に定義した場合)。それらが異なるのは、拡張性、柔軟性、および構成可能性です。
itsbruce 2013年

さまざまなサイズの鋼板が揃っている場合は、それらを一緒にボルトで固定してボックスを形成したり、溶接したりできます。狭い視点から見ると、これらは機能的には同等です。一方を簡単に再構成または拡張でき、もう一方はできないという事実を無視した場合。あなたはそれらが同等であると主張するつもりですが、あなたの基準についてもっと言ったら、私は間違って証明されて嬉しいです。
itsbruce 2013年

私はあなたが一般的に言っていることをよく知っていますが、この特定の場合の違いが何であるかはまだわかりません。1つの方法が他の方法よりも拡張性と柔軟性が高いことを示すコード例をいくつか教えていただけませんか?*拡張子付きのベースコード*セルフタイプ付きのベースコード*拡張スタイルに追加された機能*セルフタイプスタイルに追加された機能
Mark Canlas

賞金が尽きる前に、私はそれを試すことができると思います;)
itsbruce 2013年

回答:


11

特性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を使用したい人なら誰でも

  • Bを混合し、次にAをCに混合します。
  • Bのサブタイプを混合し、次にAをCに混合します。
  • AをCにミックスします。CはBのサブクラスです。

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()


勝利と上書きの機能的な違いは何ですか?どちらの場合も異なるメカニズムでAを取得していますか?そして、あなたの最初の例では...最初の段落で、なぜ特性AがSuperOfBを拡張していないのですか?どちらのメカニズムを使用しても常に問題を再構築できるように感じます。これが不可能なユースケースは見当たらないと思います。または私はあまりにも多くのものを想定しています。
Mark Canlas 2013年

ええと、Bが必要なものを定義しているのに、なぜAにBのサブクラスを拡張させたいのですか?自己参照はB(またはサブクラス)の存在を強制しますが、開発者に選択肢を与えますか?特性Aを記述した後、Bを拡張する限り、それらは特性Aを記述した後、それらを混在させることができます。
itsbruce 2013年

違いが非常に明確になるように更新されました。
itsbruce 2013年

@itsbruce概念的な違いはありますか?IS-A対HAS-A?
Jas

@Jas特性ABの間の関係のコンテキストでは、継承はIS-Aですが、入力された自己参照はHAS-A(構成的関係)を提供します。特性が混在するクラスの場合、結果はIS-Aです。
itsbruce、2014

0

自己型を拡張する場合と設定する場合の可視性と「デフォルト」実装の違いのいくつかを示すコードがあります。実際の名前の衝突がどのように解決されるかについてすでに説明した部分は示されていませんが、その代わりに何が可能で何が不可能であるかに焦点を当てています。

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必要であることを公開しないことです。の特定の特殊化が使用されていることが実際にわかる唯一のコードは、(のような)構成されたタイプを明示的に知っているコードです。BA1BThink*{X,Y}

もう1つのポイントは、他に何も指定されていない場合A2は(拡張あり)が実際に使用Bするのに対し、A1(自己型)はB、オーバーライドしない限り使用することを示さないため、オブジェクトがインスタンス化されるときに具象Bを明示的に指定する必要があることです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.