← Назад к вопросам

Для чего нужна композиция?

1.6 Junior🔥 121 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Композиция в Android и программировании

Композиция — это принцип проектирования, при котором объект содержит другие объекты (экземпляры других классов) вместо наследования от них. Это один из ключевых принципов объектно-ориентированного программирования: «Composition over Inheritance».

Основная идея

Вместо того чтобы создавать иерархию классов через наследование:

// ❌ Плохо — наследование
open class Vehicle(val speed: Int)

class Car(speed: Int) : Vehicle(speed) {
    fun honk() = println("Beep!")
}

class Truck(speed: Int) : Vehicle(speed) {
    fun loadCargo() = println("Loading...")
}

Мы используем композицию — объект содержит другие объекты:

// ✅ Хорошо — композиция
data class Engine(val speed: Int) {
    fun start() = println("Engine started")
}

data class Horn(val sound: String) {
    fun honk() = println(sound)
}

class Car(engine: Engine, horn: Horn) {
    fun drive() = engine.start()
    fun signal() = horn.honk()
}

Почему композиция лучше наследования

1. Гибкость Композиция позволяет комбинировать поведение различных объектов динамически. С наследованием вы привязаны к одной иерархии:

interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class FileLogger : Logger {
    override fun log(message: String) {
        // Запись в файл
    }
}

class UserRepository(private val logger: Logger) {
    fun saveUser(user: User) {
        // Код сохранения
        logger.log("User saved")
    }
}

2. Избежание проблем множественного наследования Заботы о diamond problem и порядке инициализации исчезают.

3. Принцип единственной ответственности (SRP) Каждый класс отвечает за одно. Композиция помогает разделить ответственность:

data class User(val id: Int, val name: String)

class UserRepository(private val database: Database) {
    fun save(user: User) = database.insert(user)
}

class UserValidator(private val emailValidator: EmailValidator) {
    fun validate(email: String) = emailValidator.isValid(email)
}

4. Тестируемость Легче создавать mock-объекты:

class UserRepositoryTest {
    @Test
    fun testSave() {
        val mockDatabase = mockk<Database>()
        val repository = UserRepository(mockDatabase)
        repository.save(User(1, "John"))
        verify { mockDatabase.insert(any()) }
    }
}

Практический пример в Android

// Компоненты
interface ApiService { suspend fun fetchUsers(): List<User> }
interface LocalDatabase { fun saveUsers(users: List<User>) }
interface Logger { fun log(msg: String) }

// Repository использует композицию
class UserRepository(
    private val apiService: ApiService,
    private val database: LocalDatabase,
    private val logger: Logger
) {
    suspend fun sync() {
        try {
            logger.log("Fetching users...")
            val users = apiService.fetchUsers()
            database.saveUsers(users)
            logger.log("Sync complete")
        } catch (e: Exception) {
            logger.log("Error: ${e.message}")
        }
    }
}

Когда использовать наследование

Наследование полезно только для:**

  • Определения общего интерфейса (абстрактные классы, интерфейсы)
  • Полиморфизма через контракты

Заключение: композиция — основной инструмент для гибкого и поддерживаемого кода. Применяй её по умолчанию.

Для чего нужна композиция? | PrepBro