В чем преимущество использования абстрактных классов вместо признаков?

303

В чем преимущество использования абстрактного класса вместо признака (помимо производительности)? Кажется, что абстрактные классы могут быть заменены чертами в большинстве случаев.

Теги:
traits

7 ответов

304
Лучший ответ

Я могу думать о двух различиях

  • Абстрактные классы могут иметь параметры конструктора, а также параметры типа. Черты могут иметь только параметры типа. Было некоторое обсуждение, что в будущем даже черты могут иметь параметры конструктора
  • Абстрактные классы полностью совместимы с Java. Вы можете вызывать их из Java-кода без каких-либо оберток. Черты полностью совместимы, только если они не содержат кода реализации
  • 151
    Очень важное дополнение: класс может наследовать от нескольких признаков, но только от одного абстрактного класса. Я думаю, что это должен быть первый вопрос, который задает разработчик при рассмотрении вопроса, который использовать почти во всех случаях.
  • 11
    lifesaver: «Черты полностью совместимы, только если они не содержат кода реализации»
Показать ещё 3 комментария
151

В разделе Scala есть раздел в разделе "Чтобы узнать или не отличиться?" , в котором рассматривается этот вопрос. Поскольку 1-е издание доступно в Интернете, я надеюсь, что все в порядке. (Любой серьезный программист Scala должен купить книгу):

Всякий раз, когда вы реализуете многократно используемую коллекцию поведения, вы будете должны решить, хотите ли вы использовать черту или абстрактный класс. Нет твердого правила, но в этом разделе содержится несколько рекомендаций для рассмотреть.

Если поведение не будет повторно использовано, сделайте его конкретным классом. Это в конце концов, не является повторным действием.

Если он может быть повторно использован в нескольких, не связанных между собой классах, сделать его чертой. Только черты могут быть смешаны в разных частях иерархии классов.

Если вы хотите наследовать его в Java-коде, используйте абстрактный класс. Поскольку черты с кодом не имеют близкого Java-аналога, он, как правило, неудобно унаследовать от признака в классе Java. Наследование от Scala класс, тем временем, в точности подобен наследованию от класса Java. В качестве одного исключения признак Scala с только абстрактными членами переводит непосредственно на интерфейс Java, поэтому вы можете свободно определять такие черты, даже если вы ожидаете, что код Java наследуется от него. См. Главу 29 для получения дополнительной информации о совместной работе с Java и Scala.

Если вы планируете распространять его в скомпилированной форме, и вы ожидаете внешнего группы, чтобы писать наследуемые от него классы, вы можете наклоняться в сторону используя абстрактный класс. Проблема заключается в том, что когда признак получает или теряет член, любые наследуемые от него классы должны быть перекомпилированы, даже если они не изменились. Если внешние клиенты будут только звонить в поведение, вместо того, чтобы наследовать от него, то использование признака в порядке.

Если эффективность очень важна, опирайтесь на использование класса. Большинство Java время выполнения делает вызов виртуального метода членом класса быстрее чем вызов метода интерфейса. Черты собираются для интерфейсы и, следовательно, могут немного снизить издержки. Однако вы должны сделать этот выбор, только если вы знаете, что этот признак в вопросе представляет собой узкое место в производительности и имеет доказательства что использование класса вместо этого фактически решает проблему.

Если вы все еще не знаете, после рассмотрения выше, тогда начните с что делает его чертой. Вы всегда можете изменить его позже, и в целом использование признака открывает больше возможностей.

Как отметил @Mushtaq Ahmed, черта не может иметь никаких параметров, переданных первому конструктору класса.

Другим отличием является лечение super.

Другое отличие между классами и признаками состоит в том, что в то время как в классах super вызовы статически связаны, в чертах они динамически связаны. Если вы пишете super.toString в классе, вы точно знаете, какая реализация метода будет вызвана. Однако, когда вы пишете одно и то же в признаке, реализация метода для вызова супервызов undefined, когда вы определяете признак.

Подробнее см. остальную часть Глава 12.

Edit:

Существует тонкая разница в том, как ведут себя абстрактные классы по сравнению с чертами. Одно из правил линеаризации заключается в том, что он сохраняет иерархию наследования классов, которая в конечном итоге пытается продвинуть абстрактные классы в цепочке, в то время как черты могут быть смешаны. В определенных обстоятельствах на самом деле предпочтительнее быть в последнем положении линеаризации класса, поэтому для этого можно было бы использовать абстрактные классы. См. ограничение линеаризации класса (порядок mixin) в Scala.

  • 0
    Очень важное дополнение: класс может наследовать от нескольких признаков, но только от одного абстрактного класса. Я думаю, что это должен быть первый вопрос, который задает разработчик при рассмотрении вопроса, который использовать почти во всех случаях.
  • 2
    If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine - может кто-нибудь объяснить, в чем здесь разница? extends против with ?
Показать ещё 1 комментарий
60

Что бы это ни стоило, Odersky et al. Программирование в Scala рекомендует, если вы сомневаетесь, вы используете черты. Вы всегда можете изменить их на абстрактные классы позже, если это необходимо.

16

Помимо того факта, что вы не можете напрямую расширять несколько абстрактных классов, но вы можете смешивать несколько признаков в классе, стоит упомянуть, что черты стекируются, так как супервызыки в признаке динамически связаны (он ссылается на класс или черта, смешанная до текущей).

От Томаса ответ в Разница между абстрактным классом и чертой:

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

При расширении абстрактного класса это показывает, что подкласс имеет аналогичный вид. Это не обязательно, когда я использую черты, я думаю.

  • 0
    Имеет ли это какое-либо практическое значение или только облегчает понимание кода?
  • 3
    Я просто имею в виду понятность кода.
6

В Программирование Scala авторы говорят, что абстрактные классы создают классическое объектно-ориентированное отношение "is-a", а черты - scala -уровень композиции.

5

Абстрактные классы могут содержать поведение - они могут параметризоваться конструкторами args (какие черты не могут) и представляют собой рабочий объект. Вместо этого черты представляют только одну функцию, интерфейс одной функциональности.

  • 8
    Надеюсь, вы не подразумеваете, что черты не могут содержать поведение. Оба могут содержать код реализации.
  • 1
    @ Митч Блевинс: Конечно нет. Они могут содержать код, но когда вы определяете trait Enumerable с большим количеством вспомогательных функций, я бы не назвал их поведением, а просто функциональностью, связанной с одной функцией.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню