Kotlin поддерживает параметры наследования через ключевое слово open, но основная рекомендация языка — соглашаться на композицию вместо наследования. Это позволяет создавать более гибкие, расширяемые системы, избегая хрупкости иерархий и проблем, связанных с глубокой наследственностью.
Композиция состоит в том, чтобы включать объект нужного типа как поле класса и делегировать ему работу, вместо того чтобы наследовать его реализацию. Kotlin облегчает паттерн делегирования с помощью ключевого слова by, позволяя автоматически делегировать реализацию интерфейсов объектам.
Пример паттерна делегирования:
interface Logger { fun log(message: String) } class ConsoleLogger : Logger { override fun log(message: String) = println(message) } class Service(private val logger: Logger) : Logger by logger { fun doAction() { log("Action done") } } fun main() { val service = Service(ConsoleLogger()) service.doAction() // Выведет: Action done }
Такой подход упрощает повторное использование кода и делает логику более модульной.
"Может ли data class наследоваться от другого класса, например от абстрактного?"
data class в Kotlin не может наследоваться от другого класса (кроме интерфейсов), поскольку data class всегда final. Исключение — интерфейсы, их реализовать можно.Пример:
abstract class Base(val name: String) data class Derived(val age: Int, val name: String) : Base(name) // Ошибка компиляции: data class не может расширять класс Base
Но возможно:
interface User data class Admin(val name: String, val rights: List<String>) : User
История
На проекте решили унаследовать несколько сервисов от общего абстрактного класса, чтобы реализовать повторяющуюся логику. В итоге получилось множество уровней наследования, усложнилася отладка, появились проблемы с тестированием. После перехода на композицию и делегирование (через интерфейсы и внедрение зависимостей) удалось упростить код, сделать его более модульным, повысить покрытие тестами.
История
Начинающий разработчик пытался расширить data class с помощью другого класса для добавления общей функциональности. Код не компилировался, но программист долго не мог понять причину (ограничения data class в Kotlin).
История
В проекте с обширной логикой логирования решили вынести функционал логирования в базовый класс. Однако с ростом системы часть сервисов требовала другой реализации логирования. Пришлось рефакторить с помощью интерфейса Logger и композиции через делегирование, что значительно упростило архитектуру.