Эта статья была опубликована Rhyme вScalaCoolБлог команды.
Черты, черты, с которыми мы одновременно знакомы и не знакомы. Привычность заключается в том, что вы обнаружите, что он очень похож на интерфейс, который вы обычно используете в Java, а странность заключается в том, что новый игровой процесс Traits заставит вас сломать познание исходного интерфейса и войти в более сложные, более продвинутые области. играть. Итак, в начале мы можемTraits
Есть предварительное понимание: это усиленная версияinterface
. Позже, узнав об этом больше, вы обнаружите, что Traits больше похожи на классы, чем на интерфейсы Java. Позже вы можете понять, что,Черты пытаются лучше интегрировать абстракцию в единое целое.
Начало работы с чертами
Чтобы избежать дорогостоящих затрат на множественное наследование (конфликт методов или полей, алмазное наследование и т. д.) в Java, разработчики Java использовали интерфейсный интерфейс. И для того, чтобы решить Java интерфейс не может быть выполненоstackable modifications
(то есть состояние объекта нельзя использовать для итерации), и поле не может быть предоставлено.В Scala мы используемTraits
Черты, а не интерфейсы.
определить черту
trait Animal {
val typeOf: String = "哺乳动物" // 带有默认值的字段
def move(): Unit = { // 带有默认实现的方法
println("walk")
}
def eat() //未实现的抽象方法
}
Приведенный выше код аналогичен следующему коду Java.
public interface Animal {
String typeOf = "哺乳动物";
default void move() {
System.out.println("walk");
}
void eat();
}
Использование ключевых слов в Scalatrait
вместоinterface
, как интерфейс Java,trait
Также могут быть реализации методов по умолчанию. То есть существуют Java-интерфейсы.trait
В принципе, они тоже есть, и реализовать их гораздо элегантнее.
Причина, по которой мы говорим что-то похожее на приведенный выше код Java, заключается в том, чтоtrait
имеет поляtypeOf
,а такжеinterface
имеет статические свойстваtypeOf
. Этоinterface
а такжеtrait
небольшая разница. Но присмотритесь и подумайте об этой разнице,Лучшее и более гибкое проектирование поля, делает ли этоtrait
Абстракции лучше организованы, что делает их лучшим целым.
mix in trait
Как и Java, Scala поддерживает только одиночное наследование, но может иметь любое количество трейтов. В Scala мы не называем интерфейсimplements
достигнуто, ноtraits
Он смешивается с классом путем смешивания.
class Bird extends Animal {
override val typeOf: String = "蛋生动物"
override def eat(): Unit = {
println("eat bugs")
}
override def move(): Unit = {
println("fly")
}
}
В приведенном выше кодеBird
Классы смешиваются в чертахAnimal
. Когда класс смешивается с несколькими чертами, вам нужно использоватьwith
ключевые слова
trait Egg
class Bird extends Animal with Egg{
override val typeOf: String = "蛋生动物"
override def eat(): Unit = {
println("eat bugs")
}
override def move(): Unit = {
println("fly")
}
}
В Scala мы быextends with
Этот синтаксис читается целиком, например, в приведенном выше коде мы будемextends Animal with Egg
рассматривать в целом, а затемBird
миксин класса. Ты чувствуешь это отсюда?trait
Попытка лучше интегрировать абстракции в единое целое.
Здесь вы можете найти это по сравнению сJava interface
,trait
больше похоже на классы. И это правда,trait
Может иметь все характеристики класса, кроме отсутствия параметров конструктора. с этой точки зренияtrait
Для достижения того же эффекта можно использовать поля конструктора. То есть вы не можете передавать параметры трейтам так же, как вы передаете параметры конструктора классам. Конкретный код здесь демонстрироваться не будет.
На самом деле, здесь мы можем просто подумать о том, почему мы должныtrait
разработан какclass
, намеренное или непреднамеренное совпадение дизайнера. На самом деле, несмотря ни на что,Я лично думаю, но с точки зрения дизайна,class
дизайн классаtrait
При большей согласованности объектами, созданными классом, можно хорошо управлять, почему бы нам не управлять нашими абстракциями так же, как мы управляем объектами?
Два основных применения черт
Traits
Два наиболее распространенных способа его использования: один похож на интерфейс Java для разработки многофункциональных интерфейсов, а другой уникален для Traits.stackable modifications
. здесь сказаноinterface
а такжеtrait
Второе отличие, Traits поддерживаетstackable modificatio
, что позволяет ему использовать состояние объекта, которое можно гибко повторять.
rich interface
Применение богатого интерфейса благодаряinterface
Поддержка функции методов по умолчанию в , с одной стороны, ослабляет прочную связь между реализацией и реализованным между классами и интерфейсами, а с другой стороны, подменяет расширяемость программы большой гибкостью.trait
Приложения в этом отношении мало чем отличаются от Java. а такжеtrait
За реализацией метода по умолчанию вinterface
серединаdefault
Метод по умолчанию.
trait Hello {
def hello(): Unit = {println("hello")
}
}
interface Hello2 {
default void hello() {...}
}
stackable modifications
оstackable modifications
, как следует из названия, мы будемmodification
сохранен вstack
в стеке. То есть мы можем итеративно обрабатывать результат операции для достижения желаемого результата. Это полезно для нужд, которые хотят распределить обработку и получить определенный результат.
Здесь мы беремprogramming in scala
пример в
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
import scala.collection.mutable.ArrayBuffer
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) {
buf += x
}
}
trait Doubling extends IntQueue {
abstract override def put(x: Int) {
super.put(2 * x)
}
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) {
super.put(x + 1)
}
}
trait Filtering extends IntQueue {
abstract override def put(x: Int) {
if (x >= 0) super.put(x)
}
}
В приведенном выше коде мы определяем абстрактную очередь сput
а такжеget
соответствующий метод реализации предоставляется в классе BasicIntQueue. При этом определяются три характеристикиDoubling
,Incrementing
,Filtering
, все они наследуют абстрактный класс IntQueue (помните, что я говорил ранее,trait
Может иметь все характеристики класса) и переопределять методы.Doubling
обработает результат *2,Incrementing
Черта добавляет +1 к результату обработки,Filtering
Значения
Мы смотрим на следующие текущие результаты
scala> val queue = (new BasicIntQueue with Incrementing with Filtering)
queue: BasicIntQueue with Incrementing with Filtering...
scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()
res15: Int = 1
scala> queue.get()
res16: Int = 2
scala> val queue = (new BasicIntQueue with Filtering with Incrementing)
queue: BasicIntQueue with Filtering with Incrementing...
scala> queue.put(-1); queue.put(0); queue.put(1)
scala> queue.get()
res17: Int = 0
scala> queue.get()
res18: Int = 1
scala> queue.get()
res19: Int = 2
Внимательно наблюдайте за приведенным выше кодом, поймите приведенный выше код, вы в основном пойметеstackable modifications
.
Во-первых, вы можете заметить, что приведенные выше два фрагмента кода в целом похожи, но дают разные результаты только из-за идиосинкразии.Filtering
а такжеIncrementing
Порядок смешивания другой. Давайте внимательнее посмотрим на реализацию метода в трейте, и мы увидим, что в трейте все проходятsuper
Ключевое слово вызывает метод родительского класса. И в этом причина описанной выше ситуации.trait
серединаsuper
да поддержкаstackable modifications
фундаментальный ключ.
существуетtrait
серединаsuper
динамически связан, иsuper
Вызов метода в другом трейте, и то, какой метод в трейте вызывается, зависит от порядка, в котором смешиваются трейты. Для общих последовательностей мы можем использовать порядок «сзади наперед», чтобы вывестиsuper
последовательность звонков.
Возьмите код выше.
new BasicIntQueue with Incrementing with Filtering
Порядок выполнения супер кода в порядке от задней части к передней.
Filtering -> Incrementing -> BasicIntQueue
приведите конкретный пример
Например, на этот раз я выполняюput(1)
кода, то в соответствии с приведенным выше порядком выполнения,
выполнить первымFiltering
изput
Метод определяет, больше ли значение 1, находит его допустимым и передает значение 1 вIncrementing
серединаput
метод,Incrementing
серединаput
Метод добавляет 1 к значению и передает его вBasicIntQueue
Затем поместите конечное значение 2 в очередь.
Процесс выполнения приведенного выше кодаstackable modifications
Основной. Итак, здесь вы можете понять приведенные выше разные результаты из-за разных порядков смешивания.
Кроме того, говоря о динамике, мы также можем кратко рассказать о ней здесь. В Яве,super
статический иtrait
серединаsuper
динамичность в резком контрасте. А преимущества и мощь, приносимые динамикой, мы также видели парочку в содержании этого раздела. На самом деле динамическое извлечение — это дизайнерская идея, и она уже давно существует вокруг нас. Например, мы знакомы с внедрением зависимостей IOC, аспектно-ориентированным программированием АОП и технологией внешнего динамического сжатия.
Черты Исследуйте
Порядок построения признаков
trait Test {
val name:String = "hello" //特质构造器的一部分
println(name); // 特质构造器的一部分
}
Как видно из приведенного выше кода, операторы выполнения, заключенные в фигурные скобки типажа, являются частью конструктора типажа.
Порядок конструкторов трейтов следующий: (ссылка из «Быстрое изучение Scala»)
- Конструктор суперкласса выполняется первым (то есть за ним следует
extends
после урока) - Инициализаторы признаков выполняются после инициализаторов суперкласса и перед инициализаторами класса.
- Признаки строятся слева направо
- Родительский признак строится первым
- конструктор класса
Например
class SavingAccount extends Account with FileLogger with ShortLogger
trait ShortLogger extends Logger
trait FileLogger extends Logger
Вышеупомянутые конструкторы будут выполняться в следующем порядке
-
Account
(суперкласс) -
Logger
(родительский признак первого признака) -
FileLogger
(первая черта) -
ShortLogger
(Второй признак слева направо, его родительский признакLogger
был построен и больше не будет строиться) -
SavingAccount
(конструктор класса)
Линеаризация
На самом деле за последовательной реализацией вышеперечисленных конструкторов стоит техника, называемая «линеаризацией».
Возьмите приведенный выше код в качестве примера
class SavingAccount extends Account with FileLogger with ShortLogger
Приведенный выше код будет линеаризован и проанализирован как
>>
означает, что правая часть будет построена первой
lin(SavingsAccount) = SavingsAccount >> lin(ShortLogger) >> lin(FileLogger) >> lin(Account)
= SavingsAccount >> (ShortLogger >> Logger) >> (FileLogger >> Logger) >> Account
= SavingsAccount >> ShortLogger >> FileLogger >> Logger >> Account
Внимательно наблюдая за результатами следующей линеаризации, вы обнаружите, что указанный выше порядок является порядком, в котором выполняются конструкторы. В то же время линеаризация также даетsuper
Порядок выполнения, например, вShortLogger
вызыватьsuper
позвонит правильноFileLogger
метод в , в то время какFileLogger
серединаsuper
позвонит правильноLogger
методы в , и так далее.
Инициализация поля признака
Поэтому, поскольку время выполнения конструктора типажа предшествует выполнению конструктора класса, особое внимание следует уделять времени выполнения полей при инициализации полей в свойстве, чтобы избежать ситуации с нулевыми указателями. Например, следующий код выдаст ошибку
trait Hello {
val name:String
val out = new PrintStream(name)
}
val test = new Test with Hello {
val name = "Rhyme" // Error 类构造器晚于特质构造器
}
Решения提前定义
или懒值
Использование предопределенного кода показано ниже
val test = new {
val name = "Rhyme" //先于所有的构造器执行
}Test with Hello
Способ определения заранее делает код менее элегантным, мы также можем использовать метод ленивых значений
Способ использования ленивого значения следующий:
trait Hello {
val name:String
lazy val out = new PrintStream(name) // 使用懒值,延迟name的初始化
}
Ленивое значение возвращается, чтобы проверить, было ли поле инициализировано перед каждым использованием, и есть определенные накладные расходы на использование. Тщательное рассмотрение перед использованием
Из-за нехватки места околоtrait
, мы остановимся здесь. Я надеюсь, что эта статья поможет вам узнать и понятьtrait
Предложите небольшую помощь. В следующей главе мы представимtrait
Чуть более продвинутое использование, типы self и типы структур.