В чем преимущество использования абстрактного класса вместо признака (помимо производительности)? Кажется, что абстрактные классы могут быть заменены чертами в большинстве случаев.
Я могу думать о двух различиях
В разделе 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.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- может кто-нибудь объяснить, в чем здесь разница? extends
против with
?
Что бы это ни стоило, 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 авторы говорят, что абстрактные классы создают классическое объектно-ориентированное отношение "is-a", а черты - scala -уровень композиции.
Абстрактные классы могут содержать поведение - они могут параметризоваться конструкторами args (какие черты не могут) и представляют собой рабочий объект. Вместо этого черты представляют только одну функцию, интерфейс одной функциональности.
trait Enumerable
с большим количеством вспомогательных функций, я бы не назвал их поведением, а просто функциональностью, связанной с одной функцией.