อะไรคือความแตกต่างระหว่างประเภทย่อยของตนเองและลักษณะย่อย?


387

ประเภทตนเองสำหรับลักษณะA:

trait B
trait A { this: B => }

บอกว่า" Aไม่สามารถผสมลงในระดับที่เป็นรูปธรรมที่ไม่ยังขยายB "

ในทางตรงกันข้ามต่อไปนี้:

trait B
trait A extends B

กล่าวว่า"ใด ๆ (ที่เป็นรูปธรรมหรือนามธรรม) ชั้นผสมในAก็จะได้รับการผสมใน B"

ข้อความทั้งสองนี้ไม่ได้มีความหมายเหมือนกันหรือไม่ ดูเหมือนว่าประเภทของตัวเองจะให้บริการเพียงเพื่อสร้างความเป็นไปได้ของข้อผิดพลาดในการรวบรวมเวลาที่เรียบง่าย

ฉันพลาดอะไรไป


จริง ๆ แล้วฉันสนใจที่นี่ในความแตกต่างระหว่างประเภทของตัวเองและคลาสย่อยในลักษณะ ฉันรู้ถึงการใช้งานทั่วไปสำหรับประเภทตนเอง ฉันหาเหตุผลไม่ได้ว่าทำไมพวกเขาถึงไม่ทำแบบเดียวกันกับการพิมพ์ย่อย
เดฟ

32
หนึ่งสามารถใช้พารามิเตอร์ประเภทภายในประเภทตัวเอง: trait A[Self] {this: Self => }ถูกกฎหมายtrait A[Self] extends Selfไม่ได้
Blaisorblade

3
ประเภทตนเองสามารถเป็นคลาสได้ แต่คุณลักษณะไม่สามารถสืบทอดจากคลาสได้
cvogt

10
@cvogt: ลักษณะสามารถสืบทอดจากคลาส (อย่างน้อยเป็น 2.10): pastebin.com/zShvr8LX
Erik Kaplun

1
@Blaisorblade: นั่นไม่ใช่สิ่งที่สามารถแก้ไขได้ด้วยการออกแบบภาษาเล็ก ๆ อีกครั้ง แต่ไม่ใช่ข้อ จำกัด พื้นฐานหรือไม่? (อย่างน้อยจากมุมมองของคำถาม)
Erik Kaplun

คำตอบ:


273

มันถูกใช้เป็นส่วนใหญ่สำหรับการฉีดพึ่งพาเช่นในรูปแบบเค้ก มีบทความที่ดีที่ครอบคลุมรูปแบบต่าง ๆ ของการฉีดพึ่งพาในสกาล่ารวมถึงรูปแบบเค้กอยู่ หากคุณ Google "Cake Pattern and Scala" คุณจะได้รับลิงก์มากมายรวมถึงการนำเสนอและวิดีโอ สำหรับตอนนี้นี่คือการเชื่อมโยงไปยังคำถามอื่น

ทีนี้ถึงความแตกต่างระหว่างประเภทของตัวเองกับการขยายลักษณะนั่นเป็นเรื่องง่าย ถ้าคุณบอกว่าB extends Aแล้วB เป็น Aเมื่อคุณใช้ประเภทตนเองB ต้อง Aมีข้อกำหนดเฉพาะสองข้อที่สร้างขึ้นด้วยประเภทตนเอง:

  1. ถ้าBจะขยายออกไปแล้วคุณต้องAไปผสมใน
  2. เมื่อระดับที่เป็นรูปธรรมที่สุดขยาย / ผสมในลักษณะเหล่านี้ชั้นบาง / 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ที่มีให้

สำหรับกรณีการใช้งานจริงเพิ่มเติมโปรดดูลิงค์ที่จุดเริ่มต้นของคำตอบนี้! แต่หวังว่าตอนนี้คุณจะได้รับมัน


3
ขอบคุณ ลวดลายเค้กคือ 90% ของสิ่งที่ฉันหมายถึงทำไมฉันพูดคุยเกี่ยวกับ hype รอบประเภทตนเอง ... เป็นที่แรกที่ฉันเห็นหัวข้อ ตัวอย่างของ Jonas Boner นั้นยอดเยี่ยมเพราะมันตอกย้ำประเด็นคำถามของฉัน ถ้าคุณเปลี่ยนชนิดตนเองในตัวอย่างฮีตเตอร์ของเขาที่จะ subtraits แล้วสิ่งที่จะเป็นความแตกต่าง (นอกเหนือจากข้อผิดพลาดที่คุณได้รับเมื่อกำหนด ComponentRegistry ถ้าคุณไม่ผสมในสิ่งที่ถูกต้องหรือไม่
เดฟ

29
@Dave: คุณหมายความว่าtrait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponentอย่างไร ที่จะทำให้WarmerComponentImplมีอินเทอร์เฟซเหล่านั้น พวกเขาจะมีอะไรที่ยื่นออกมาWarmerComponentImplซึ่งเป็นสิ่งที่ผิดอย่างชัดเจนตามที่มันเป็นไม่ได้และไม่SensorDeviceComponent OnOffDeviceComponentเป็นชนิดที่ตนเองอ้างอิงเหล่านี้จะใช้ได้เฉพาะWarmerComponentImplเพื่อ Listสามารถนำมาใช้เป็นArrayและในทางกลับกัน แต่พวกเขาก็ไม่ได้เหมือนกัน
Daniel C. Sobral

10
ขอบคุณแดเนียล นี่อาจเป็นความแตกต่างที่สำคัญที่ฉันกำลังมองหา ปัญหาที่เกิดขึ้นจริงคือการใช้คลาสย่อยจะทำให้การทำงานในอินเตอร์เฟสที่คุณไม่ต้องการทำได้ มันเป็นผลมาจากการละเมิดกฎ "เป็นส่วนหนึ่งของการ" ทฤษฎีสำหรับลักษณะ ประเภทตนเองแสดงความสัมพันธ์ "ใช้ - a" ระหว่างชิ้นส่วน
เดฟ

11
@Rodney ไม่มันไม่ควร ในความเป็นจริงโดยใช้กับชนิดด้วยตนเองเป็นสิ่งที่ฉันมองลงมาเพราะมันเงาไม่มีเหตุผลที่ดีเดิมthis this
Daniel C. Sobral

9
@opensas self: Dep1 with Dep2 =>ลอง
Daniel C. Sobral

156

ประเภทตนเองช่วยให้คุณสามารถกำหนดการอ้างอิงตามวัฏจักร ตัวอย่างเช่นคุณสามารถบรรลุสิ่งนี้:

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 (การสร้างบทสเปรดชีต UI) ที่กล่าวถึง:

ในตัวอย่างสเปร็ดชีต Class Model สืบทอดจาก Evaluator และเข้าถึงวิธีการประเมิน ในการใช้วิธีอื่น Class Evaluator จะกำหนดประเภทของตัวเองให้เป็นแบบจำลองดังนี้:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...

หวังว่านี่จะช่วยได้


3
ฉันไม่ได้พิจารณาสถานการณ์นี้ นี่เป็นตัวอย่างแรกของบางสิ่งที่ฉันเห็นว่าไม่เหมือนกับตัวเองเหมือนกับ subclass อย่างไรก็ตามดูเหมือนว่าจะเป็นแบบขอบเคซี่ย์และที่สำคัญกว่านั้นก็ดูเหมือนว่าเป็นความคิดที่ไม่ดี คุณคิดว่านี่เป็นความแตกต่างที่สำคัญที่สุดหรือไม่?
เดฟ

4
ฉันคิดอย่างนั้น ฉันไม่เห็นเหตุผลอื่น ๆ ว่าทำไมฉันถึงชอบประเภทตนเองเพื่อขยายอนุประโยค ประเภทตัวเองเป็น verbose พวกเขาไม่ได้รับการสืบทอด (ดังนั้นคุณต้องเพิ่มประเภทตนเองในประเภทย่อยทั้งหมดเป็นพิธีกรรม) และคุณสามารถเห็นสมาชิกเท่านั้น แต่ไม่สามารถแทนที่พวกเขา ฉันตระหนักถึงลวดลายเค้กและโพสต์มากมายที่พูดถึงประเภทตนเองสำหรับ DI แต่อย่างใดฉันไม่มั่นใจ ฉันสร้างแอปตัวอย่างที่นี่มานานแล้ว ( bitbucket.org/mushtaq/scala-di ) ดูที่โฟลเดอร์ / src / configs โดยเฉพาะ ฉันได้รับ DI เพื่อแทนที่การกำหนดค่าสปริงที่ซับซ้อนโดยไม่มีประเภทตนเอง
Mushtaq Ahmed

Mushtaq เราอยู่ในข้อตกลง ฉันคิดว่าคำแถลงของดาเนียลเกี่ยวกับการไม่เปิดเผยฟังก์ชั่นที่ไม่ได้ตั้งใจนั้นเป็นสิ่งสำคัญ แต่อย่างที่คุณกล่าวไว้มันมีมุมมองแบบมิชชันของ 'ฟีเจอร์' ... ที่คุณไม่สามารถแทนที่ฟังก์ชั่นนี้ นี่บอกได้ชัดเจนว่าเมื่อไรการออกแบบจะเรียกอีกแบบหนึ่ง ฉันจะหลีกเลี่ยงประเภทตนเองจนกว่าฉันจะพบความต้องการที่แท้จริง - เช่นถ้าฉันเริ่มใช้วัตถุเป็นโมดูลตามที่ Daniel ชี้ให้เห็น ฉันกำลังอ้างอิงอัตโนมัติกับพารามิเตอร์โดยนัยและวัตถุ bootstrapper ตรงไปตรงมา ฉันชอบความเรียบง่าย
Dave

@ DanielC.Sobral อาจจะขอบคุณความคิดเห็นของคุณ แต่ในขณะนี้มันมี upvotes มากกว่า anser ของคุณ การถอนเงินทั้งคู่ :)
rintcius

ทำไมไม่สร้างเพียงหนึ่งตัวอักษร AB? เนื่องจากคุณสมบัติ A และ B จะต้องรวมกันในชั้นเรียนสุดท้ายเสมอทำไมต้องแยกพวกเขาออกจากกันตั้งแต่แรก
คนรวยโอลิเวอร์

56

ความแตกต่างเพิ่มเติมอย่างหนึ่งคือประเภทตัวเองสามารถระบุประเภทที่ไม่ใช่ชั้นเรียนได้ ตัวอย่างเช่น

trait Foo{
   this: { def close:Unit} => 
   ...
}

ประเภทตัวเองที่นี่เป็นประเภทโครงสร้าง ผลที่ได้คือบอกว่าอะไรก็ตามที่ผสมใน Foo จะต้องใช้วิธีการส่งคืนหน่วย "ปิด" แบบไม่มีอาร์กิวเมนต์ สิ่งนี้ช่วยให้มิกซ์อินที่ปลอดภัยสำหรับการพิมพ์เป็ด


41
จริงๆแล้วคุณสามารถใช้การสืบทอดกับชนิดโครงสร้างได้เช่นกัน: abstract class A ขยาย {def close: Unit}
Adrian

12
ฉันคิดว่าการพิมพ์แบบโครงสร้างกำลังใช้การสะท้อนดังนั้นใช้เฉพาะเมื่อไม่มีตัวเลือกอื่น ...
Eran Medan

@ เอเดรียฉันเชื่อว่าความคิดเห็นของคุณไม่ถูกต้อง `คลาสนามธรรม A ขยาย {def close: Unit}` เป็นเพียงคลาสนามธรรมที่มี Object superclass เป็นเพียงไวยากรณ์ที่อนุญาตของ Scala สำหรับการแสดงออกที่ไร้สาระ คุณสามารถ `class X ขยาย {def f = 1}; ใหม่ X (). f` ตัวอย่าง
Alexey

1
@ Alexey ฉันไม่เห็นว่าทำไมตัวอย่างของคุณ (หรือของฉัน) ไร้สาระ
เอเดรียน

1
@Adrian, เทียบเท่ากับabstract class A extends {def close:Unit} abstract class A {def close:Unit}ดังนั้นจึงไม่เกี่ยวข้องกับประเภทโครงสร้าง
Alexey

13

ส่วนที่ 2.3 "คำอธิบายประกอบ Selftype" ของต้นฉบับ Scala แบบScalable Component Scalaable Paper ของ Martin Odersky อธิบายถึงวัตถุประสงค์ของ selftype นอกเหนือจากการผสมผสาน mixin เป็นอย่างดี: ให้ทางเลือกอื่นในการเชื่อมโยงคลาสกับประเภทนามธรรม

ตัวอย่างที่ให้ไว้ในบทความเป็นดังต่อไปนี้และดูเหมือนว่าจะไม่ได้มีผู้สื่อข่าว subclass ที่สง่างาม:

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;
  }
}

สำหรับผู้ที่สงสัยว่าทำไมการแบ่งคลาสย่อยจะไม่แก้ปัญหานี้ส่วนที่ 2.3 ยังกล่าวถึงสิ่งนี้:“ ตัวถูกดำเนินการขององค์ประกอบมิกซ์อินแต่ละตัวด้วย C_0 กับ ... ที่มี C_n ต้องอ้างอิงคลาส กลไกการแต่งเพลง mixin ไม่อนุญาตให้ C_i ใด ๆ อ้างถึงประเภทนามธรรม ข้อ จำกัด นี้ทำให้สามารถตรวจสอบความคลุมเครือและความขัดแย้งทับซ้อนในจุดที่สร้างคลาสได้”
ลุคเมาเรอร์

12

อีกสิ่งที่ไม่ได้กล่าวถึง: เนื่องจากประเภทตัวเองไม่ได้เป็นส่วนหนึ่งของลำดับชั้นของคลาสที่ต้องการพวกเขาสามารถแยกออกจากการจับคู่รูปแบบโดยเฉพาะอย่างยิ่งเมื่อคุณจับคู่อย่างละเอียดกับลำดับชั้นที่ปิดสนิท สิ่งนี้จะสะดวกเมื่อคุณต้องการจำลองพฤติกรรมแบบมุมฉากเช่น:

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

10

TL; DR สรุปคำตอบอื่น ๆ :

  • ประเภทที่คุณขยายนั้นจะได้รับการสืบทอดประเภท แต่ประเภทตนเองไม่ได้

    เช่นช่วยให้คุณสามารถใช้วิธีการใช้ได้เฉพาะกับสัตว์เคี้ยวเอื้องเช่นclass Cow { this: FourStomachs } digestGrassลักษณะที่ขยาย Cow อย่างไรก็ตามจะไม่มีสิทธิ์พิเศษดังกล่าว บนมืออื่น ๆ ที่class Cow extends FourStomachsจะเปิดเผย ให้ทุกคนที่digestGrassextends Cow

  • ประเภทตนเองอนุญาตให้พึ่งพาวงจรขยายประเภทอื่น ๆ ไม่ได้


9

เริ่มจากการพึ่งพาวงจร

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

แม้ว่าหากคุณแทนที่สมาชิกของประเภทตนเองคุณจะไม่สามารถเข้าถึงสมาชิกดั้งเดิมซึ่งยังสามารถเข้าถึงได้ผ่านการใช้การสืบทอดขั้นสูง ดังนั้นสิ่งที่ได้รับมากกว่าการใช้มรดกคือ:

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

ตอนนี้ฉันไม่สามารถเรียกร้องให้เข้าใจรายละเอียดปลีกย่อยทั้งหมดของลวดลายเค้กได้ แต่มันทำให้ฉันรู้สึกว่าวิธีการบังคับใช้โมดุลที่เป็นโมดูล่านั้นได้มาจากองค์ประกอบมากกว่าการสืบทอดหรือประเภทของตัวเอง

เวอร์ชันการสืบทอดสั้นกว่า แต่เหตุผลหลักที่ฉันชอบการสืบทอดมากกว่าประเภทตัวเองคือฉันพบว่ามันยากกว่ามากในการรับลำดับการเริ่มต้นที่ถูกต้องกับประเภทของตนเอง อย่างไรก็ตามมีบางสิ่งที่คุณสามารถทำกับประเภทของตัวเองที่คุณไม่สามารถทำกับมรดก ประเภทตนเองสามารถใช้ประเภทในขณะที่การสืบทอดต้องมีลักษณะหรือชั้นเรียนเช่น:

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 }

ประเด็นหนึ่งที่ควรเอาใจใส่มากกว่านี้ก็คือคุณลักษณะนั้นสามารถขยายชั้นเรียนได้ ขอบคุณ David Maclver ที่ชี้เรื่องนี้ออกมา นี่คือตัวอย่างจากรหัสของฉัน:

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 Frame ดังนั้นจึงสามารถใช้เป็นประเภทตัวเองแล้วผสมในตอนท้าย (ที่ instantiation) อย่างไรก็ตามval geomRจำเป็นต้องเริ่มต้นใหม่ก่อนที่จะถูกใช้โดยการสืบทอดคุณสมบัติ ดังนั้นเราจึงจำเป็นชั้นเรียนในการบังคับใช้ initialisation geomRก่อน ชั้นScnVistaนั้นจะสามารถสืบทอดจากหลายลักษณะ orthogonal ซึ่งตัวเองสามารถสืบทอด การใช้พารามิเตอร์หลายประเภท (ข้อมูลทั่วไป) นำเสนอรูปแบบทางเลือกของโมดุล


7
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
}

4

ประเภทตัวเองช่วยให้คุณระบุประเภทที่ได้รับอนุญาตให้ผสมในลักษณะ ตัวอย่างเช่นหากคุณมีลักษณะที่มีประเภทตัวเองลักษณะCloseableนั้นจะรู้ว่าสิ่งเดียวที่ได้รับอนุญาตให้ผสมในนั้นจะต้องใช้Closeableส่วนต่อประสาน


3
@Blaisorblade: ฉันสงสัยว่าคุณอาจมีคำตอบที่ผิดของ kikibobo หรือไม่ประเภทของตัวตนในลักษณะที่แน่นอนช่วยให้คุณสามารถ จำกัด ประเภทที่อาจผสมในนั้นและเป็นส่วนหนึ่งของประโยชน์ ตัวอย่างเช่นถ้าเรากำหนดtrait A { self:B => ... }แล้วการประกาศX with Aจะใช้ได้เฉพาะเมื่อ X ขยาย B ใช่คุณสามารถพูดได้X with A with Qโดยที่ Q ไม่ขยาย B แต่ฉันเชื่อว่าประเด็นของ kikibobo คือ X มีข้อ จำกัด เช่นนั้น หรือฉันคิดถึงอะไรบางอย่าง?
AmigoNico

1
ขอบคุณคุณพูดถูก การลงคะแนนของฉันถูกล็อค แต่โชคดีที่ฉันสามารถแก้ไขคำตอบแล้วเปลี่ยนการลงคะแนนของฉัน
Blaisorblade

1

อัปเดต:ความแตกต่างหลักคือประเภทตัวเองสามารถขึ้นอยู่กับหลายคลาส (ฉันยอมรับว่าเป็นตัวพิมพ์เล็ก ๆ น้อย ๆ มุม) ตัวอย่างเช่นคุณสามารถมี

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;
}

นี้จะช่วยให้เพิ่มEmployeemixin เพียงเพื่อสิ่งที่เป็น subclass ของและPerson Expenseแน่นอนว่ามันมีความหมายก็ต่อเมื่อExpenseขยายออกไปPersonหรือกลับกัน ประเด็นก็คือการใช้ประเภทตนเองEmployeeสามารถเป็นอิสระจากลำดับชั้นของชั้นเรียนที่มันขึ้นอยู่กับ มันไม่สนใจในสิ่งที่ขยายสิ่งที่ - หากคุณเปลี่ยนลำดับชั้นของExpenseVS Person, Employeeคุณไม่ได้มีการปรับเปลี่ยน


พนักงานไม่จำเป็นต้องเป็นชั้นเรียนในการสืบทอดจากบุคคล ลักษณะสามารถขยายชั้นเรียน หากลักษณะนิสัยของพนักงานขยายบุคคลแทนการใช้ประเภทตนเองตัวอย่างจะยังคงทำงาน ฉันพบว่าตัวอย่างของคุณน่าสนใจ แต่ดูเหมือนจะไม่ได้อธิบายกรณีการใช้งานสำหรับประเภทตนเอง
Morgan Creighton

@ MorganCreighton ยุติธรรมพอฉันไม่ทราบว่าลักษณะสามารถขยายชั้นเรียนได้ ฉันจะลองคิดดูถ้าฉันสามารถหาตัวอย่างที่ดีกว่าได้
Petr Pudlák

ใช่มันเป็นคุณสมบัติภาษาที่น่าแปลกใจ หากลักษณะนิสัยของพนักงานขยายบุคคลในชั้นเรียนดังนั้นในที่สุดสิ่งใดก็ตามที่ "อยู่ใน" พนักงานก็จะต้องขยายออกไปเช่นกัน แต่ข้อ จำกัด ดังกล่าวยังคงมีอยู่หากพนักงานใช้ประเภทตนเองแทนการขยายบุคคล ไชโย, Petr!
Morgan Creighton

1
ฉันไม่เห็นว่าทำไม "สิ่งนี้มีความหมายเฉพาะถ้าค่าใช้จ่ายขยายบุคคลหรือในทางกลับกัน"
Robin Green

0

ในกรณีแรกลักษณะย่อยหรือคลาสย่อยของ B สามารถผสมกับสิ่งที่ใช้ A. ดังนั้น B อาจเป็นลักษณะนามธรรม


ไม่ B สามารถเป็น "ลักษณะที่เป็นนามธรรม" ได้ทั้งสองกรณี ดังนั้นจึงไม่มีความแตกต่างจากมุมมองนั้น
Robin Green
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.