В чем преимущество использования абстрактных классов вместо признаков?
В чем преимущество использования абстрактного класса вместо черты (помимо производительности)? Похоже, что в большинстве случаев абстрактные классы могут быть заменены признаками.
8 ответов
Я могу думать о двух различиях
- Абстрактные классы могут иметь параметры конструктора, а также параметры типа. Черты могут иметь только параметры типа. Было некоторое обсуждение, что в будущем даже черты могут иметь параметры конструктора
- Абстрактные классы полностью совместимы с Java. Вы можете вызывать их из кода Java без каких-либо оболочек. Черты полностью совместимы, только если они не содержат кода реализации
В Scala есть раздел "Программирование, черта или нет?" который решает этот вопрос. Так как 1-е издание доступно в Интернете, я надеюсь, что все здесь можно процитировать. (Любой серьезный программист Scala должен купить книгу):
Всякий раз, когда вы реализуете повторно используемую коллекцию поведения, вам придется решить, хотите ли вы использовать черту или абстрактный класс. Не существует строгого правила, но этот раздел содержит несколько рекомендаций для рассмотрения.
Если поведение не будет использоваться повторно, сделайте его конкретным классом. Это не многоразовое поведение в конце концов.
Если это может быть повторно использовано в нескольких, не связанных между собой классах, сделайте это признаком. Только черты могут быть смешаны в разных частях иерархии классов.
Если вы хотите наследовать от него в коде Java, используйте абстрактный класс. Поскольку черты с кодом не имеют близкого аналога Java, обычно бывает неудобно наследовать черту в классе Java. В то же время наследование от класса Scala точно такое же, как наследование от Java-класса. В качестве одного исключения, черта Scala, содержащая только абстрактные элементы, транслируется непосредственно в интерфейс Java, поэтому вы можете свободно определять такие черты, даже если ожидаете, что код Java будет наследоваться от него. См. Главу 29 для получения дополнительной информации о совместной работе с Java и Scala.
Если вы планируете распространять его в скомпилированной форме и ожидаете, что внешние группы будут писать классы, унаследованные от него, вы можете склоняться к использованию абстрактного класса. Проблема в том, что когда признак получает или теряет член, любые классы, которые наследуют его, должны быть перекомпилированы, даже если они не изменились. Если внешние клиенты будут вызывать только поведение, а не наследовать его, тогда использование черты вполне нормально.
Если эффективность очень важна, склоняйтесь к использованию класса. Большинство сред выполнения Java делают вызов виртуального метода членом класса более быстрой операцией, чем вызов метода интерфейса. Черты компилируются в интерфейсы и, следовательно, могут немного снизить производительность. Однако этот выбор следует делать только в том случае, если вы знаете, что рассматриваемая черта представляет собой узкое место в производительности, и у вас есть доказательства того, что использование класса вместо этого фактически решает проблему.
Если вы все еще не знаете, после рассмотрения вышеизложенного, начните с того, что сделайте это чертой характера. Вы всегда можете изменить его позже, и в целом использование черты оставляет больше возможностей открытым.
Как упоминал @Mushtaq Ahmed, в признаке не может быть никаких параметров, передаваемых первичному конструктору класса.
Еще одно отличие заключается в лечении super
,
Другое различие между классами и чертами заключается в том, что в то время как в классах,
super
вызовы статически связаны, в чертах, они динамически связаны. Если ты пишешьsuper.toString
в классе вы точно знаете, какая реализация метода будет вызвана. Однако, когда вы пишете то же самое в признаке, реализация метода, вызываемая для супер-вызова, не определяется при определении признака.
См. Остальную часть главы 12 для более подробной информации.
Изменить 1 (2013):
Существует небольшая разница в поведении абстрактных классов по сравнению с чертами. Одно из правил линеаризации заключается в том, что оно сохраняет иерархию наследования классов, которая имеет тенденцию выдвигать абстрактные классы позднее в цепочке, в то время как признаки могут быть легко смешаны. В определенных обстоятельствах на самом деле предпочтительнее находиться в последней позиции линеаризации классов Таким образом, абстрактные классы могут быть использованы для этого. Смотрите линеаризацию ограничивающего класса (порядок смешивания) в Scala.
Изменить 2 (2018):
Начиная с Scala 2.12, поведение бинарной совместимости черты изменилось. До версии 2.12 добавление или удаление члена признака требовало перекомпиляции всех классов, которые наследуют признак, даже если классы не изменились. Это связано с тем, как черты кодировались в JVM.
Начиная с Scala 2.12, черты компилируются в интерфейсы Java, поэтому требования немного ослабли. Если признак выполняет одно из следующих действий, его подклассы все еще требуют перекомпиляции:
- определяющие поля (
val
или жеvar
, но константа в порядке -final val
без типа результата)- вызов супер
- оператор инициализатора в теле
- продление класса
- опираясь на линеаризацию, чтобы найти реализации в правильном супертрейте
Но если этого не происходит, теперь вы можете обновить его, не нарушая бинарную совместимость.
Для чего бы это ни стоило, Odersky et al. Программирование в 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 авторы говорят, что абстрактные классы создают классические объектно-ориентированные отношения "есть", в то время как черты - это scala-образ композиции.
Абстрактные классы могут содержать поведение - они могут параметризоваться с помощью аргументов конструктора (что не может быть признаков) и представлять работающий объект. Черты вместо этого просто представляют одну особенность, интерфейс одной функциональности.
- Класс может наследовать от нескольких признаков, но только от одного абстрактного класса.
- Абстрактные классы могут иметь параметры конструктора, а также параметры типа. Черты могут иметь только параметры типа. Например, вы не можете сказать trait t(i: Int) { }; Параметр i недопустим.
- Абстрактные классы полностью совместимы с Java. Вы можете вызывать их из кода Java без каких-либо оболочек. Черты полностью совместимы, только если они не содержат кода реализации.