Câu trả lời:
Tôi có thể nghĩ về hai sự khác biệt
Có một phần trong Lập trình trong Scala gọi là "Để tính trạng, hay không tính trạng?" giải quyết câu hỏi này Vì phiên bản 1 có sẵn trực tuyến, tôi hy vọng sẽ ổn khi trích dẫn toàn bộ điều ở đây. (Bất kỳ lập trình viên Scala nghiêm túc nào cũng nên mua sách):
Bất cứ khi nào bạn thực hiện một bộ sưu tập hành vi có thể sử dụng lại, bạn sẽ phải quyết định xem bạn muốn sử dụng một đặc điểm hay một lớp trừu tượng. Không có quy tắc vững chắc, nhưng phần này có một vài hướng dẫn để xem xét.
Nếu hành vi sẽ không được sử dụng lại , thì hãy biến nó thành một lớp cụ thể. Đó không phải là hành vi tái sử dụng sau tất cả.
Nếu nó có thể được sử dụng lại trong nhiều lớp , không liên quan , hãy biến nó thành một đặc điểm. Chỉ các đặc điểm có thể được trộn vào các phần khác nhau của hệ thống phân cấp lớp.
Nếu bạn muốn kế thừa từ nó trong mã Java , hãy sử dụng một lớp trừu tượng. Do các đặc điểm với mã không có sự tương tự Java gần gũi, nên có xu hướng lúng túng khi kế thừa từ một đặc điểm trong lớp Java. Kế thừa từ một lớp Scala, trong khi đó, chính xác giống như kế thừa từ một lớp Java. Như một ngoại lệ, một đặc điểm Scala chỉ có các thành viên trừu tượng dịch trực tiếp sang giao diện Java, vì vậy bạn nên thoải mái xác định các đặc điểm đó ngay cả khi bạn mong muốn mã Java được thừa hưởng từ nó. Xem Chương 29 để biết thêm thông tin về cách làm việc với Java và Scala cùng nhau.
Nếu bạn có kế hoạch phân phối nó ở dạng biên dịch và bạn mong đợi các nhóm bên ngoài viết các lớp kế thừa từ nó, bạn có thể nghiêng về việc sử dụng một lớp trừu tượng. Vấn đề là khi một đặc điểm được hoặc mất một thành viên, bất kỳ lớp nào được thừa hưởng từ nó phải được biên dịch lại, ngay cả khi chúng không thay đổi. Nếu khách hàng bên ngoài sẽ chỉ gọi vào hành vi, thay vì thừa hưởng từ nó, thì sử dụng một đặc điểm là tốt.
Nếu hiệu quả là rất quan trọng , hãy nghiêng về việc sử dụng một lớp học. Hầu hết các thời gian chạy Java làm cho một lời gọi phương thức ảo của một thành viên lớp hoạt động nhanh hơn so với một lời gọi phương thức giao diện. Các đặc điểm được biên dịch sang các giao diện và do đó có thể trả một khoản chi phí hiệu năng thấp. Tuy nhiên, bạn chỉ nên đưa ra lựa chọn này nếu bạn biết rằng đặc điểm trong câu hỏi tạo thành một nút cổ chai hiệu năng và có bằng chứng cho thấy việc sử dụng một lớp thay vì thực sự giải quyết vấn đề.
Nếu bạn vẫn chưa biết , sau khi xem xét những điều trên, thì hãy bắt đầu bằng cách biến nó thành một đặc điểm. Bạn luôn có thể thay đổi nó sau này và nói chung, sử dụng một đặc điểm sẽ mở ra nhiều tùy chọn hơn.
Như @Mushtaq Ahmed đã đề cập, một đặc điểm không thể có bất kỳ tham số nào được chuyển đến hàm tạo chính của một lớp.
Một sự khác biệt là điều trị super
.
Sự khác biệt khác giữa các lớp và các đặc điểm là trong khi trong các lớp,
super
các lệnh gọi bị ràng buộc tĩnh, trong các tính trạng, chúng bị ràng buộc động. Nếu bạn viếtsuper.toString
trong một lớp, bạn sẽ biết chính xác phương thức thực hiện nào sẽ được gọi. Tuy nhiên, khi bạn viết điều tương tự trong một đặc điểm, việc triển khai phương thức để gọi siêu cuộc gọi là không xác định khi bạn xác định đặc điểm đó.
Xem phần còn lại của Chương 12 để biết thêm chi tiết.
Chỉnh sửa 1 (2013):
Có một sự khác biệt tinh tế trong cách hành xử của các lớp trừu tượng so với các đặc điểm. Một trong những quy tắc tuyến tính hóa là nó bảo toàn hệ thống phân cấp kế thừa của các lớp, chúng có xu hướng đẩy các lớp trừu tượng về sau trong chuỗi trong khi các đặc điểm có thể được trộn lẫn một cách vui vẻ. , vì vậy các lớp trừu tượng có thể được sử dụng cho điều đó. Xem ràng buộc tuyến tính hóa lớp (thứ tự mixin) trong Scala .
Chỉnh sửa 2 (2018):
Kể từ Scala 2.12, hành vi tương thích nhị phân của đặc điểm đã thay đổi. Trước 2.12, việc thêm hoặc xóa thành viên vào tính trạng yêu cầu biên dịch lại tất cả các lớp kế thừa tính trạng, ngay cả khi các lớp không thay đổi. Điều này là do cách các đặc điểm được mã hóa trong JVM.
Kể từ Scala 2.12, các đặc điểm biên dịch thành các giao diện Java , do đó, yêu cầu này đã giảm bớt một chút. Nếu đặc điểm thực hiện bất kỳ điều nào sau đây, các lớp con của nó vẫn yêu cầu biên dịch lại:
- xác định các trường (
val
hoặcvar
, nhưng hằng số là ok -final val
không có loại kết quả)- gọi
super
- báo cáo khởi tạo trong cơ thể
- mở rộng một lớp học
- dựa vào tuyến tính hóa để tìm ra các triển khai ở vùng bên phải
Nhưng nếu đặc điểm không có, bây giờ bạn có thể cập nhật nó mà không phá vỡ tính tương thích nhị phân.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Ai đó có thể giải thích sự khác biệt ở đây là gì? extends
vs with
?
extends
và with
. Nó hoàn toàn là cú pháp. Nếu bạn thừa kế từ nhiều mẫu, mẫu đầu tiên extend
, tất cả các mẫu khác nhận được with
, đó là mẫu đó. Hãy nghĩ về with
dấu phẩy : class Foo extends Bar, Baz, Qux
.
Đối với bất cứ điều gì nó có giá trị, Lập trình của Oderky et al trong Scala khuyên rằng, khi bạn nghi ngờ, bạn sử dụng các đặc điểm. Bạn luôn có thể thay đổi chúng thành các lớp trừu tượng sau này nếu cần.
Khác với thực tế là bạn không thể trực tiếp mở rộng nhiều lớp trừu tượng, nhưng bạn có thể trộn nhiều đặc điểm vào một lớp, điều đáng nói là các đặc điểm này có thể xếp chồng lên nhau, vì các siêu lệnh trong một đặc điểm bị ràng buộc động (nó đang đề cập đến một lớp hoặc tính trạng được trộn lẫn trước đó hiện tại).
Từ câu trả lời của Thomas về sự khác biệt giữa lớp trừu tượng và đặc điểm :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
Trong Lập trình Scala, các tác giả nói rằng các lớp trừu tượng tạo ra một mối quan hệ "is-a" theo hướng đối tượng cổ điển trong khi các đặc điểm là một cách sáng tác.
Các lớp trừu tượng có thể chứa hành vi - Chúng có thể được tham số hóa với các hàm tạo (các đặc điểm không thể) và đại diện cho một thực thể làm việc. Thay vào đó, các đặc điểm chỉ đại diện cho một tính năng duy nhất, một giao diện của một chức năng.
trait Enumerable
có nhiều hàm trợ giúp, tôi sẽ không gọi chúng là hành vi mà chỉ là chức năng được kết nối với một tính năng.