Відповіді:
Я можу придумати дві відмінності
У програмі Scala є розділ "Програмування ", який називається "Навчити чи не рисувати?" яка вирішує це питання. Оскільки 1-е видання доступне в Інтернеті, я сподіваюся, що це нормально, щоб процитувати тут все. (Будь-який серйозний програміст Scala повинен придбати книгу):
Кожного разу, коли ви реалізуєте колекцію поведінки, що використовується повторно, вам доведеться вирішувати, чи хочете ви використовувати ознаку чи абстрактний клас. Не існує твердого правила, але цей розділ містить кілька рекомендацій, які слід врахувати.
Якщо поведінка не буде повторно використана , то зробіть це конкретним класом. Зрештою, це не повторна поведінка.
Якщо його можна повторно використовувати в декількох непов'язаних класах , зробіть це ознакою. Тільки риси можна змішувати в різних частинах ієрархії класів.
Якщо ви хочете успадкувати його в коді Java , використовуйте абстрактний клас. Оскільки риси з кодом не мають близького аналога Java, це, як правило, незручно успадковувати від ознаки класу Java. Тим часом успадкування від класу Scala - це точно як успадкування від класу Java. Як єдиний виняток, риса Scala з лише абстрактними членами перекладається безпосередньо на інтерфейс Java, тому вам слід сміливо визначати такі риси, навіть якщо ви очікуєте, що від неї успадкує код Java. Див. Главу 29 для отримання додаткової інформації про спільну роботу з Java та Scala.
Якщо ви плануєте поширити його у складеному вигляді , і ви очікуєте, що зовнішні групи будуть писати класи, успадковані від нього, ви можете схилитися до використання абстрактного класу. Проблема полягає в тому, що коли ознака набирає або втрачає член, будь-які класи, які успадковують його, повинні бути перекомпільовані, навіть якщо вони не змінилися. Якщо зовнішні клієнти будуть закликати лише поведінку, а не успадковувати її, то використання ознаки прекрасно.
Якщо ефективність дуже важлива , схиляйтесь до використання класу. Більшість програм Java робить виклик віртуального методу члена класу швидшою операцією, ніж виклик методу інтерфейсу. Характеристики збираються в інтерфейси, і тому вони можуть платити невеликі накладні витрати. Однак ви повинні зробити цей вибір лише в тому випадку, якщо ви знаєте, що ця ознака є вузьким місцем продуктивності та маєте докази того, що використання класу замість цього фактично вирішує проблему.
Якщо ви все ще не знаєте , розглядаючи сказане, тоді почніть, роблячи це як ознаку. Ви завжди можете змінити його пізніше, і загалом, використовуючи ознаку, відкриваються більше варіантів.
Як зазначав @Mushtaq Ахмед, ознака не може мати жодних параметрів, переданих первинному конструктору класу.
Ще одна відмінність - це лікування super
.
Інша відмінність між класами та ознаками полягає в тому, що в той час, як у класах
super
виклики є статично пов'язаними, у ознаках вони динамічно пов'язані. Якщо ви пишетеsuper.toString
в класі, ви точно знаєте, до якого методу буде застосовано реалізацію методу. Коли ви пишете те саме в ознаці, проте реалізація методу для виклику супервиклику не визначається, коли ви визначаєте ознаку.
Докладніше див. У решті глави 12 .
Редагувати 1 (2013):
Існує тонка різниця в тому, як поводяться абстрактні класи порівняно з ознаками. Одне з правил лінеаризації полягає в тому, що він зберігає ієрархію успадкування класів, яка має тенденцію висувати абстрактні класи пізніше по ланцюгу, в той час як ознаки можуть бути щасливо змішані. За певних обставин, фактично бажано перебувати в останньому положенні класу лінеаризації. , тому абстрактні класи можуть бути використані для цього. Дивіться обмежувальну лінеаризацію класів (міксин порядок) у Scala .
Редагування 2 (2018):
Станом на Scala 2.12 поведінка бінарної сумісності ознак змінилося. До 2.12 додавання або видалення члена до ознаки вимагало перекомпіляції всіх класів, які успадковують ознаку, навіть якщо класи не змінилися. Це пов'язано з тим, як риси кодувались у JVM.
Як і в Scala 2.12, риси компілюються в інтерфейси Java , тому вимога трохи розслабилася. Якщо ознака робить щось із наступного, її підкласи все ще потребують перекомпіляції:
- визначення полів (
val
абоvar
, але константа в порядку -final val
без типу результату)- дзвінок
super
- заяви ініціалізатора в тілі
- розширення класу
- опираючись на лінеаризацію, щоб знайти реалізацію в потрібному суперрейтингу
Але якщо ознака цього немає, тепер ви можете оновити її, не порушуючи бінарної сумісності.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Чи міг би хтось пояснити, у чому тут різниця? extends
проти with
?
extends
і with
. Це суто синтаксично. Якщо ви успадковуєте з декількох шаблонів, перший отримує extend
, всі інші отримують with
, ось і все. Подумайте , with
як кома: class Foo extends Bar, Baz, Qux
.
Що б там не було, Програмування Одерського та ін в Scala рекомендує, коли сумніваєтесь, використовувати риси. Ви завжди можете змінити їх на абстрактні класи пізніше, якщо потрібно.
Крім того, що ви не можете безпосередньо поширювати кілька абстрактних класів, але ви можете поєднувати декілька ознак у клас, варто згадати, що ознаки можуть бути складеними, оскільки супервиклики в ознаці динамічно пов'язані (це стосується класу або ознаки, змішаної раніше поточний).
З відповіді Томаса в різниці між абстрактним класом і ознакою :
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
В програмуванні Scala автори кажуть, що абстрактні класи складають класичний об'єктно-орієнтований взаємозв'язок, тоді як риси є шкалою композиції.
Абстрактні класи можуть містити поведінку - вони можуть параметризуватися за допомогою аргументів конструктора (які ознаки не можуть) та представляти собою робочу сутність. Натомість риси представляють собою єдину функцію, інтерфейс однієї функціональності.
trait Enumerable
з великою кількістю допоміжних функцій, я б не називав їх поведінкою, а лише функціональністю, пов'язаною з однією функцією.