7. Используйте делегирование интерфейса

В предыдущем задании были рассмотрены абстрактные классы и интерфейсы. Делегирование интерфейсов — это продвинутый метод проектирования, при котором методы интерфейса реализуются вспомогательным (или делегатским) объектом, который затем используется классом. Этот метод может быть полезен, когда интерфейс используется в ряде несвязанных классов. Необходимая функциональность интерфейса реализуется в отдельном вспомогательном классе. Затем каждый из несвязанных классов использует экземпляр этого вспомогательного класса для получения необходимой функциональности.

В этом задании вы используете делегирование интерфейса для добавления функциональности классу.

Шаг 1: Создайте новый интерфейс

  1. В файле AquariumFish.kt удалите AquariumFishкласс. Вместо наследования от AquariumFishкласса, Plecostomusмы Sharkсобираемся реализовать интерфейсы как для действий с рыбами, так и для их цвета.
  2. Создайте новый интерфейс, FishColorкоторый определяет цвет в виде строки.
interface FishColor {
    val color: String
}
  1. Необходимо внести изменения Plecostomusдля реализации двух интерфейсов: FishAction, и FishColor. Вам нужно переопределить методы colorfrom FishColorи eat()from FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Измените свой Sharkкласс так, чтобы он также реализовывал два интерфейса, FishActionи FishColor, вместо наследования от AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "grey"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. В итоге ваш код должен выглядеть примерно так:
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.

  1. В файле AquariumFish.kt создайте объект для GoldColor. Переопределите цвет.
object GoldColor : FishColor {
   override val color = "gold"
}

Шаг 3: Добавить делегирование интерфейса для FishColor

Теперь вы готовы использовать делегирование интерфейса.

  1. В файле AquariumFish.kt удалите переопределение colorиз Plecostomus.
  2. Измените Plecostomusкласс, чтобы он получал свой цвет из GoldColor. Для этого добавьте by GoldColorв объявление класса делегирование. Это означает, что вместо реализации FishColor, используйте реализацию, предоставляемую GoldColor. Таким образом, каждый раз color, когда к , происходит делегирование GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

В текущем виде все экземпляры класса Plecostomus будут иметь цвет «золотой». Но на самом деле эти рыбы бывают разных цветов. Это можно исправить, добавив параметр конструктора для цвета, используя GoldColorв качестве цвета по умолчанию Plecostomus.

  1. Измените 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.

  1. В файле AquariumFish.kt создайте PrintingFishActionкласс, реализующий интерфейс `AquariumFish` FishAction, который принимает String foodв качестве параметра конструктора объект `AquariumFish`, а затем выводит информацию о том, чем питается рыба.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. На Plecostomusзанятии удалите функцию переопределения eat(), поскольку вы замените её делегированием.
  2. В заявлении Plecostomusделегат FishActionпередает PrintingFishAction."eat algae"
  3. При таком количестве делегирований в теле класса нет кода Plecostomus, поэтому удалите {}, поскольку все переопределения обрабатываются делегированием интерфейса.
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Если бы вы создали аналогичный дизайн для Shark, следующая диаграмма представляла бы как классы , Sharkтак и Plecostomusклассы . Оба класса состоят из интерфейсов PrintingFishActionи FishColor, но реализация делегируется им.

a7556eed5ec884a3.png

Делегирование интерфейсов — мощный инструмент, и вам, как правило, следует подумать о том, как его использовать, когда вы могли бы использовать абстрактный класс в другом языке. Оно позволяет использовать композицию для подключения различных вариантов поведения, вместо того чтобы создавать множество подклассов, каждый из которых специализируется на чем-то своем.

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


Последнее изменение: Воскресенье, 5 апреля 2026, 14:10