Статьи

Groovy 2.3 вводит черты

Несколько дней назад вышла вторая бета-версия Groovy 2.3 . Одна из главных новых возможностей Groovy 2.3 — это черты . Черта — это повторно используемый набор методов и полей, которые могут быть добавлены к одному или нескольким классам. Класс может быть составлен из нескольких признаков без использования множественного наследования (и, следовательно, избежания проблемы алмаза ).

Основное использование

Следующий фрагмент кода показывает основное определение черты в Groovy 2.3.

1
2
3
4
5
trait SwimmingAbility {
  def swim() {
    println "swimming.."
  }
}

Определение черты выглядит очень похоже на определение класса. Эта черта определяет единственный метод swim (). Мы можем добавить эту черту в класс, используя ключевое слово Implements:

1
2
3
class Goldfish implements SwimmingAbility {
  ..
}

Теперь мы можем вызывать методы swim () для объектов Goldfish:

1
2
def goldfish = new Goldfish()
goldfish.swim()

До сих пор мы могли бы достичь того же, используя наследование. Разница в том, что мы можем добавить несколько признаков к одному классу. Итак, давайте определим еще одну черту:

1
2
3
4
5
trait FlyingAbility {
  def fly() {
    println "flying.."
  }
}

Теперь мы можем создать новый класс, который использует обе черты:

1
2
3
class Duck implements SwimmingAbility, FlyingAbility {
  ..
}

Утки могут плавать и летать сейчас:

1
2
3
def duck = new Duck()
duck.swim()
duck.fly()

это внутри черты

Внутри черты ключевое слово this представляет экземпляр реализации. Это означает, что вы можете написать следующее:

1
2
3
4
5
trait FlyingAbility {
  def fly() {
    println "${this.class.name} is flying.."
  }
}

В случае класса Duck сверху это напечатает

1
Duck is flying..

если мы вызываем duck.fly ().

Более сложный пример

Теперь давайте посмотрим на пример, который показывает некоторые особенности черт Groovy

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
trait Trader {
 
  int availableMoney = 0
  private int tradesDone = 0
 
  def buy(Item item) {
    if (item.price <= availableMoney) {
      availableMoney -= item.price
      tradesDone += 1
      println "${getName()} bought ${item.name}"
    }
  }
 
  def sell(Item item) {
    ..
  }
 
  abstract String getName()
}

Как и в классах Groovy, свойства поддерживают свойства. Здесь свойство availableMoney станет частным, и будут сгенерированы публичные методы получения / установки. Эти методы могут быть доступны при реализации классов. tradesDone — это закрытая переменная, доступ к которой невозможно получить за пределами черты Trader. В рамках этой черты мы определили абстрактный метод getName (). Этот метод должен быть реализован классами, которые используют эту черту.

Давайте создадим класс, который реализует нашу черту Trader:

1
2
3
4
5
6
7
8
class Merchant implements Trader {
 
  String name
 
  String getName() {
    return this.name
  }
}

Торговец теперь может покупать предметы:

1
2
3
4
5
6
7
def bike = new Item(name: 'big red bike', price: 750)
def paul = new Merchant(name: 'paul')
 
paul.availableMoney = 2000
paul.buy(bike) // prints "paul bought big red bike"
 
println paul.availableMoney // 1250

Исходя из черт, переопределения и разрешения конфликтов

Черта может наследовать функциональность от другой черты, используя ключевое слово extends:

1
2
3
4
5
6
7
8
trait Dealer {
  def getProfit() { ... }
  def deal() { ... }
}
 
trait CarDealer extends Dealer {
  def deal() { ... }
}

Здесь черта CarDealer расширяет Dealer и переопределяет метод deal () Dealer.

Методы черты также могут быть переписаны в реализации классов:

1
2
3
class OnlineCarDealer implements CarDealer {
  def deal() { ... }
}

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

1
2
3
4
5
6
7
8
9
trait Car {
  def drive() { ... }
}
 
trait Bike {
  def drive() { ... }
}
 
class DrivingThing implements Car, Bike { ... }

В такой ситуации выигрывает последняя заявленная черта (в данном примере Bike).

Вывод

Я думаю, что черты — очень полезная концепция, и я рад видеть их в Groovy. Помимо Groovy черты mixins работают во время компиляции и поэтому могут быть доступны из кода Java. Для дальнейшего чтения я могу порекомендовать документацию Groovy 2.3 Trait .