Яка перевага використання абстрактних класів замість ознак?


371

Яка перевага використання абстрактного класу замість ознаки (крім продуктивності)? Здається, абстрактні класи в більшості випадків можуть бути замінені рисами.

Відповіді:


371

Я можу придумати дві відмінності

  1. Абстрактні класи можуть мати параметри конструктора, а також параметри типу. Риси можуть мати лише параметри типу. Була певна дискусія, що в майбутньому навіть риси можуть мати конструкторські параметри
  2. Абстрактні класи повністю взаємодіють з Java. Ви можете зателефонувати їм з коду Java без обгортки. Риси є повністю сумісними лише тоді, коли вони не містять коду реалізації

172
Дуже важливий додаток: Клас може успадковувати з декількох ознак, але лише один абстрактний клас. Я думаю, що це має бути першим питанням, яке розробник задає, коли розглядає, який використовувати майже у всіх випадках.
БАР

15
рятувальник: "Риси повністю взаємодіють, лише якщо вони не містять коду реалізації"
Морж Кіт

2
абстрактно - коли колективна поведінка, що визначає або веде до об'єкта (гілки об'єкта), але ще не складається як (готовий) об'єкт. Риси, коли вам потрібно викликати можливості, тобто можливості ніколи не виникають із створенням об'єкта, він розвивається або вимагається, коли об'єкт виходить з ізоляції і повинен спілкуватися.
Раміз Уддін

5
Друга різниця не існує в Java8, подумайте.
Duong Nguyen

14
За Scala 2.12 ознака компілюється в інтерфейс Java 8 - scala-lang.org/news/2.12.0#traits-compile-to-interfaces .
Кевін Мередіт

209

У програмі 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
  • заяви ініціалізатора в тілі
  • розширення класу
  • опираючись на лінеаризацію, щоб знайти реалізацію в потрібному суперрейтингу

Але якщо ознака цього немає, тепер ви можете оновити її, не порушуючи бінарної сумісності.


2
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine- Чи міг би хтось пояснити, у чому тут різниця? extendsпроти with?
0fnt

2
@ 0fnt Його відмінність не полягає у розширенні проти с. Що він говорить, це те, що якщо ви змішуєте лише ознаки в межах однієї компіляції, проблеми бінарної сумісності не застосовуються. Однак якщо ваш API призначений для того, щоб дозволити користувачам самостійно змішуватись у рисах, вам доведеться турбуватися про бінарну сумісність.
John Colanduoni

2
@ 0fnt: Абсолютно немає семантичної різниці між extendsі with. Це суто синтаксично. Якщо ви успадковуєте з декількох шаблонів, перший отримує extend, всі інші отримують with, ось і все. Подумайте , withяк кома: class Foo extends Bar, Baz, Qux.
Йорг W Міттаг


20

Крім того, що ви не можете безпосередньо поширювати кілька абстрактних класів, але ви можете поєднувати декілька ознак у клас, варто згадати, що ознаки можуть бути складеними, оскільки супервиклики в ознаці динамічно пов'язані (це стосується класу або ознаки, змішаної раніше поточний).

З відповіді Томаса в різниці між абстрактним класом і ознакою :

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

9

Під час розширення абстрактного класу це показує, що підклас має подібний вид. Думаю, це не обов'язково, коли я використовую риси.


Чи це має якісь практичні наслідки, чи це лише полегшує розуміння коду?
Ральф

8

В програмуванні Scala автори кажуть, що абстрактні класи складають класичний об'єктно-орієнтований взаємозв'язок, тоді як риси є шкалою композиції.


5

Абстрактні класи можуть містити поведінку - вони можуть параметризуватися за допомогою аргументів конструктора (які ознаки не можуть) та представляти собою робочу сутність. Натомість риси представляють собою єдину функцію, інтерфейс однієї функціональності.


8
Сподіваємось, ви не маєте на увазі, що риси не можуть містити поведінку. Обидва можуть містити код реалізації.
Мітч Блевінс

1
@Mitch Blevins: Звичайно, ні. Вони можуть містити код, але коли ви визначаєте trait Enumerableз великою кількістю допоміжних функцій, я б не називав їх поведінкою, а лише функціональністю, пов'язаною з однією функцією.
Даріо

4
@Dario Я бачу "поведінку" та "функціональність" як синоніми, тому вважаю вашу відповідь дуже заплутаною.
Девід Дж.

3
  1. Клас може успадковувати з декількох ознак, але лише один абстрактний клас.
  2. Абстрактні класи можуть мати параметри конструктора, а також параметри типу. Риси можуть мати лише параметри типу. Наприклад, ви не можете сказати ознаку t (i: Int) {}; параметр i незаконний.
  3. Абстрактні класи повністю взаємодіють з Java. Ви можете зателефонувати їм з коду Java без обгортки. Риси є повністю сумісними лише тоді, коли вони не містять коду реалізації.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.