Sự khác biệt giữa kiểu tự và tính trạng di truyền trong Scala là gì?


9

Khi Googled, nhiều phản hồi cho chủ đề này xuất hiện. Tuy nhiên, tôi không cảm thấy như bất kỳ ai trong số họ làm tốt công việc minh họa sự khác biệt giữa hai tính năng này. Vì vậy, tôi muốn thử một lần nữa, cụ thể là ...

Điều gì có thể được thực hiện với kiểu tự chứ không phải với sự kế thừa và ngược lại?

Đối với tôi, cần có một số khác biệt về thể chất, có thể định lượng giữa hai loại, nếu không chúng chỉ khác nhau về mặt danh nghĩa.

Nếu Trait A mở rộng B hoặc tự loại B, cả hai đều không minh họa rằng B là một yêu cầu? Đâu là sự khác biệt?


Tôi cảnh giác với các điều khoản bạn đã đặt trên tiền thưởng. Đối với một điều, xác định sự khác biệt "vật lý", cho rằng đây là tất cả phần mềm. Ngoài ra, đối với bất kỳ đối tượng tổng hợp nào bạn tạo bằng mixins, có thể bạn có thể tạo một cái gì đó gần đúng trong chức năng với tính kế thừa - nếu bạn xác định hàm hoàn toàn theo phương thức hiển thị. Nơi họ sẽ khác nhau là ở khả năng mở rộng, linh hoạt và khả năng kết hợp.
itbruce 25/11/13

Nếu bạn có một loại các tấm thép có kích thước khác nhau, bạn có thể ghép chúng lại với nhau để tạo thành một hộp hoặc bạn có thể hàn chúng. Từ một quan điểm hẹp, những điều này sẽ tương đương về chức năng - nếu bạn bỏ qua thực tế là người ta có thể dễ dàng cấu hình lại hoặc mở rộng và người khác thì không thể. Tôi có cảm giác rằng bạn sẽ tranh luận rằng chúng tương đương nhau, mặc dù tôi rất vui khi được chứng minh là sai nếu bạn nói thêm về tiêu chí của mình.
itbruce

Tôi không quen với những gì bạn nói chung, nhưng tôi vẫn không hiểu sự khác biệt trong trường hợp cụ thể này. Bạn có thể cung cấp một số ví dụ mã cho thấy một phương thức có thể mở rộng và linh hoạt hơn phương thức kia không? * Mã cơ sở có tiện ích mở rộng * Mã cơ sở có loại tự * Tính năng được thêm vào kiểu tiện ích mở rộng * Tính năng được thêm vào kiểu tự loại
Mark Canlas

OK, nghĩ mình có thể cố gắng mà trước khi bounty hết;)
itsbruce

Câu trả lời:


11

Nếu tính trạng A kéo dài B, thì việc trộn vào A cung cấp cho bạn chính xác B cộng với bất cứ điều gì A thêm hoặc kéo dài. Ngược lại, nếu tính trạng A có một tham chiếu tự được gõ rõ ràng là B, thì lớp cha cuối cùng cũng phải trộn vào B hoặc một kiểu con cháu của B (và trộn nó trước , điều này rất quan trọng).

Đó là sự khác biệt quan trọng nhất. Trong trường hợp đầu tiên, loại B chính xác được kết tinh tại điểm A mở rộng nó. Trong lần thứ hai, người thiết kế lớp cha sẽ quyết định phiên bản B nào được sử dụng, tại điểm mà lớp cha được tạo.

Một sự khác biệt khác là nơi A và B cung cấp các phương thức cùng tên. Trong đó A mở rộng B, phương thức của A ghi đè B's. Khi A được trộn sau B, phương thức của A đơn giản là thắng.

Tham chiếu tự đánh máy cho bạn tự do hơn nhiều; khớp nối giữa A và B bị lỏng.

CẬP NHẬT:

Vì bạn không rõ về lợi ích của những khác biệt này ...

Nếu bạn sử dụng thừa kế trực tiếp, thì bạn tạo đặc điểm A là B + A. Bạn đã thiết lập mối quan hệ trong đá.

Nếu bạn sử dụng một tham chiếu tự đánh máy, thì bất cứ ai muốn sử dụng đặc điểm A của bạn trong lớp C đều có thể

  • Trộn B rồi A vào C.
  • Trộn một kiểu con của B rồi A vào C.
  • Trộn A vào C, trong đó C là lớp con của B.

Và đây không phải là giới hạn của các tùy chọn của họ, theo cách Scala cho phép bạn khởi tạo một đặc điểm trực tiếp với một khối mã làm hàm tạo của nó.

Về sự khác biệt giữa chiến thắng của phương pháp A , bởi vì A được trộn lẫn sau cùng, so với A mở rộng B, hãy xem xét điều này ...

Khi bạn trộn trong một chuỗi các tính trạng, bất cứ khi nào phương thức foo()được gọi, trình biên dịch sẽ đi đến đặc điểm cuối cùng được trộn vào để tìm kiếm foo(), sau đó (nếu không tìm thấy), nó đi qua chuỗi bên trái cho đến khi tìm thấy một đặc điểm thực hiện foo()và sử dụng cái đó. A cũng có tùy chọn để gọi super.foo(), nó cũng đi qua chuỗi bên trái cho đến khi nó tìm thấy một triển khai, v.v.

Vì vậy, nếu A có bản tự tham chiếu đến B và người viết của A biết rằng B thực hiện foo(), A có thể gọi super.foo()biết rằng nếu không có gì khác cung cấp foo(), B sẽ. Tuy nhiên, người tạo ra lớp C có tùy chọn loại bỏ bất kỳ đặc điểm nào khác trong đó thực hiện foo()và thay vào đó A sẽ nhận được điều đó.

Một lần nữa, đây là mạnh hơn rất nhiều và ít hạn chế hơn so với A mở rộng B và trực tiếp gọi phiên bản của B foo().


Sự khác biệt về chức năng giữa A thắng so với A ghi đè là gì? Tôi nhận được A trong cả hai trường hợp thông qua các cơ chế khác nhau? Và trong ví dụ đầu tiên của bạn ... Trong đoạn đầu tiên của bạn, tại sao không có đặc điểm A mở rộng SuperOfB? Nó chỉ cảm thấy như chúng ta luôn có thể sửa chữa vấn đề bằng cách sử dụng một trong hai cơ chế. Tôi đoán tôi không thấy trường hợp sử dụng trong trường hợp này là không thể. Hoặc tôi đang giả định quá nhiều thứ.
Đánh dấu Canlas

Ừm, tại sao bạn muốn có A mở rộng một lớp con của B, nếu B xác định những gì bạn cần? Tự tham chiếu buộc B (hoặc một lớp con) có mặt, nhưng đưa ra lựa chọn cho nhà phát triển? Họ có thể trộn lẫn vào thứ họ viết sau khi bạn viết đặc điểm A, miễn là nó kéo dài B. Tại sao chỉ giới hạn họ với những gì có sẵn khi bạn viết đặc điểm A?
itbruce

Cập nhật để làm cho sự khác biệt siêu rõ ràng.
itbruce

@itsbruce có sự khác biệt về khái niệm? IS-A so với HAS-A?
Jas

@Jas Trong bối cảnh mối quan hệ giữa các tính trạng AB , tính kế thừa là IS-A trong khi tự nhập tham chiếu cho HAS-A (một mối quan hệ thành phần). Đối với lớp mà các đặc điểm được trộn lẫn, kết quả là IS-A , bất kể.
itbruce 16/12/14

0

Tôi có một số mã minh họa một số khác biệt về khả năng hiển thị của wrt và triển khai "mặc định" khi mở rộng so với cài đặt kiểu tự. Nó không minh họa bất kỳ phần nào được thảo luận về cách giải quyết xung đột tên thực tế, mà thay vào đó tập trung vào những gì có thể và không thể làm được.

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
}

Một sự khác biệt quan trọng IMO là A1không tiết lộ rằng nó cần Bbất cứ thứ gì chỉ nhìn thấy nó A1(như được minh họa trong các phần nâng cao). Mã duy nhất thực sự sẽ thấy rằng một chuyên môn cụ thể Bđược sử dụng, là mã biết rõ ràng về loại cấu thành (như Think*{X,Y}).

Một điểm khác là A2(với phần mở rộng) sẽ thực sự sử dụng Bnếu không có gì khác được chỉ định trong khi A1(kiểu tự) không nói rằng nó sẽ sử dụng Btrừ khi bị ghi đè, một B cụ thể phải được đưa ra rõ ràng khi các đối tượng được khởi tạo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.