Что такое наследование в объектно-ориентированном программировании?

Ответ

Наследование — это механизм ООП, который позволяет одному классу (дочернему, подклассу) наследовать свойства, методы и другие характеристики другого класса (родительского, суперкласса). Это основа для создания иерархий "is-a" (является).

Основные концепции в Swift:

  • Базовый класс: Класс, не наследующий от других.
  • Подкласс: Класс, который наследует от суперкласса. Может:
    • Добавлять новые свойства и методы.
    • Переопределять унаследованные методы, свойства, сабскрипты с помощью ключевого слова override.
    • Обращаться к реализации суперкласса через super.
  • Одиночное наследование: Класс в Swift может наследовать только от одного суперкласса.
  • Запрет наследования: Ключевое слово final предотвращает дальнейшее наследование от класса или переопределение конкретного метода.

Пример:

// Суперкласс (базовый класс)
class Vehicle {
    var currentSpeed: Double = 0.0
    var description: String {
        return "движется со скоростью (currentSpeed) км/ч"
    }
    func makeSound() {
        // Универсальный звук, который будет переопределён
    }
}

// Подкласс
class Bicycle: Vehicle {
    var hasBasket = false
    // Наследует currentSpeed и description
}

// Подкласс с переопределением
class Car: Vehicle {
    var gear = 1
    // Переопределение вычисляемого свойства
    override var description: String {
        return super.description + " на передаче (gear)"
    }
    // Переопределение метода
    override func makeSound() {
        print("Beep beep!")
    }
}

let bike = Bicycle()
bike.currentSpeed = 15
print(bike.description) // "движется со скоростью 15.0 км/ч"

let car = Car()
car.currentSpeed = 60
car.gear = 3
print(car.description) // "движется со скоростью 60.0 км/ч на передаче 3"
car.makeSound() // "Beep beep!"

Альтернативы: Для большей гибкости и избежания хрупких иерархий часто предпочитают композицию (протоколы, dependency injection) вместо глубокого наследования.

Ответ 18+ 🔞

Давай разберём эту штуку про наследование, а то звучит как какая-то семейная драма с наследством, только для классов.

Смотри, представь себе, есть у тебя какой-нибудь базовый класс, типа Vehicle (транспорт). Ну, как бы общее понятие. У него есть какие-то свойства: currentSpeed, какая-то description. Он умеет makeSound() (хотя звук у абстрактного транспорта — это пиздец какой-то абстрактный свист в вакууме).

class Vehicle {
    var currentSpeed: Double = 0.0
    var description: String {
        return "движется со скоростью (currentSpeed) км/ч"
    }
    func makeSound() {
        // Тишина, блядь. Абстракция.
    }
}

И вот появляется его сынок, подкласс Bicycle. Велосипед, ёпта. Он наследует ВСЁ от родителя. То есть у него автоматом появляются и currentSpeed, и description. Но он может ещё и своё добавить — корзинку, например. Он как бы говорит: «Я — транспорт (is-a), но ещё и с корзинкой».

class Bicycle: Vehicle {
    var hasBasket = false // А вот и корзинка, мать его!
}

А теперь смотри сюда, самый сок — переопределение. Появляется класс Car. Машина — тоже транспорт. Но она не может просто «делать звук» как абстрактная хуйня. Ей надо «бип-бип» орать. И в описании хочется ещё передачу указать.

Так вот, чтобы сказать системе: «Отстань, родитель, я сам знаю, как это делать», используется слово override. Это как крик подростка «Я сам!», только для кода. А через super можно позвать родительскую реализацию, типа «ладно, пап, извини, но я всё равно сделаю по-своему».

class Car: Vehicle {
    var gear = 1
    // Переопределяем описание
    override var description: String {
        // Сперва берём то, что дал родитель...
        return super.description + " на передаче (gear)"
    }
    // А тут полностью заменяем звук
    override func makeSound() {
        print("Beep beep!") // Вот так, блядь, а не тишина!
    }
}

И вот что получается в итоге:

let bike = Bicycle()
bike.currentSpeed = 15
print(bike.description) // "движется со скоростью 15.0 км/ч" — всё от родителя

let car = Car()
car.currentSpeed = 60
car.gear = 3
print(car.description) // "движется со скоростью 60.0 км/ч на передаче 3" — родительское + своё
car.makeSound() // "Beep beep!" — своё, ебать!

Но есть нюанс, Карл! В Swift нельзя быть мультиинструменталом — один класс может наследоваться только от одного родителя. Многомужества нет, только одиночное наследование.

А если ты хочешь запретить кому-то быть родителем или переопределять твои методы — поставь перед классом или методом слово final. Это как табличка «Не влезай, убьёт!». Наследников не будет, иди нахуй.

И последнее, самое важное. Глубокие иерархии наследования — это иногда пиздец как сложно и хрупко. Как карточный домик, тронул одно — всё ебнулось. Поэтому умные дяди часто советуют вместо «является» (наследование) использовать «имеет» (композиция, протоколы). Это как вместо того, чтобы быть сыном знаменитого актёра и всю жизнь оправдывать ожидания, лучше просто взять у него денег в долг (протокол) и делать что хочешь. Гибче, блядь, и голова не болит.

Вот и вся магия. Не так страшен чёрт, как его малюют. Главное — не создавать монстров с десятью уровнями наследования, а то сам потом будешь плакать, пытаясь понять, откуда у объекта взялось свойство someShit.