7. Используйте делегирование интерфейса
В предыдущем задании были рассмотрены абстрактные классы и интерфейсы. Делегирование интерфейсов — это продвинутый метод проектирования, при котором методы интерфейса реализуются вспомогательным (или делегатским) объектом, который затем используется классом. Этот метод может быть полезен, когда интерфейс используется в ряде несвязанных классов. Необходимая функциональность интерфейса реализуется в отдельном вспомогательном классе. Затем каждый из несвязанных классов использует экземпляр этого вспомогательного класса для получения необходимой функциональности.
В этом задании вы используете делегирование интерфейса для добавления функциональности классу.
Шаг 1: Создайте новый интерфейс
- В файле AquariumFish.kt удалите
AquariumFishкласс. Вместо наследования отAquariumFishкласса,PlecostomusмыSharkсобираемся реализовать интерфейсы как для действий с рыбами, так и для их цвета. - Создайте новый интерфейс,
FishColorкоторый определяет цвет в виде строки.
interface FishColor {
val color: String
}
- Необходимо внести изменения
Plecostomusдля реализации двух интерфейсов:FishAction, иFishColor. Вам нужно переопределить методыcolorfromFishColorиeat()fromFishAction.
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- Измените свой
Sharkкласс так, чтобы он также реализовывал два интерфейса,FishActionиFishColor, вместо наследования отAquariumFish.
class Shark: FishAction, FishColor {
override val color = "grey"
override fun eat() {
println("hunt and eat fish")
}
}
- В итоге ваш код должен выглядеть примерно так:
package example.myapp
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
class Shark: FishAction, FishColor {
override val color = "grey"
override fun eat() {
println("hunt and eat fish")
}
}
Шаг 2: Создайте класс-синглтон.
Далее вы реализуете настройку для части делегирования, создав вспомогательный класс, реализующий интерфейс FishColor. Вы создаете базовый класс с именем, GoldColorкоторый реализует интерфейс FishColor— он просто указывает, что его цвет — золотой.
Нет смысла создавать несколько экземпляров класса GoldColor, потому что все они будут делать одно и то же. Поэтому Kotlin позволяет объявлять класс, в котором можно создать только один его экземпляр, используя ключевое слово `class` objectвместо `class` class. Kotlin создаст этот единственный экземпляр, и на него будет ссылаться имя класса. Затем все остальные объекты смогут использовать только этот единственный экземпляр. Вы не можете создать другие экземпляры этого класса. Если вы знакомы с паттерном «синглтон» , вот как реализуются синглтоны в Kotlin.
- В файле AquariumFish.kt создайте объект для
GoldColor. Переопределите цвет.
object GoldColor : FishColor {
override val color = "gold"
}
Шаг 3: Добавить делегирование интерфейса для FishColor
Теперь вы готовы использовать делегирование интерфейса.
- В файле AquariumFish.kt удалите переопределение
colorизPlecostomus. - Измените
Plecostomusкласс, чтобы он получал свой цвет изGoldColor. Для этого добавьтеby GoldColorв объявление класса делегирование. Это означает, что вместо реализацииFishColor, используйте реализацию, предоставляемуюGoldColor. Таким образом, каждый разcolor, когда к , происходит делегированиеGoldColor.
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
В текущем виде все экземпляры класса Plecostomus будут иметь цвет «золотой». Но на самом деле эти рыбы бывают разных цветов. Это можно исправить, добавив параметр конструктора для цвета, используя GoldColorв качестве цвета по умолчанию Plecostomus.
- Измените
Plecostomusкласс так, чтобы он принимал в качестве параметра объект, передаваемыйfishColorчерез конструктор, и установите значение по умолчанию равнымGoldColor. Измените делегирование сby GoldColorнаby fishColor.
class Plecostomus(fishColor: FishColor = GoldColor): FishAction,
FishColor by fishColor {
override fun eat() {
println("eat algae")
}
}
Шаг 4: Добавьте делегирование интерфейса для FishAction.
Аналогичным образом, вы можете использовать делегирование интерфейса для FishAction.
- В файле AquariumFish.kt создайте
PrintingFishActionкласс, реализующий интерфейс `AquariumFish`FishAction, который принимаетStringfoodв качестве параметра конструктора объект `AquariumFish`, а затем выводит информацию о том, чем питается рыба.
class PrintingFishAction(val food: String) : FishAction {
override fun eat() {
println(food)
}
}
- На
Plecostomusзанятии удалите функцию переопределенияeat(), поскольку вы замените её делегированием. - В заявлении
PlecostomusделегатFishActionпередаетPrintingFishAction."eat algae" - При таком количестве делегирований в теле класса нет кода
Plecostomus, поэтому удалите{}, поскольку все переопределения обрабатываются делегированием интерфейса.
class Plecostomus (fishColor: FishColor = GoldColor):
FishAction by PrintingFishAction("eat algae"),
FishColor by fishColor
Если бы вы создали аналогичный дизайн для Shark, следующая диаграмма представляла бы как классы , Sharkтак и Plecostomusклассы . Оба класса состоят из интерфейсов PrintingFishActionи FishColor, но реализация делегируется им.

Делегирование интерфейсов — мощный инструмент, и вам, как правило, следует подумать о том, как его использовать, когда вы могли бы использовать абстрактный класс в другом языке. Оно позволяет использовать композицию для подключения различных вариантов поведения, вместо того чтобы создавать множество подклассов, каждый из которых специализируется на чем-то своем.
Композиция часто приводит к лучшей инкапсуляции , меньшей связанности (взаимозависимости), более чистым интерфейсам и более удобному коду. По этим причинам использование композиции с интерфейсами является предпочтительным подходом к проектированию. С другой стороны, наследование от абстрактного класса, как правило, является естественным решением для некоторых задач. Поэтому следует отдавать предпочтение композиции, но когда наследование имеет смысл, Kotlin позволяет использовать и его!