Основы Swift / 12.3. Наследование


Видео


Наследование
Класс может наследовать методы, свойства и другие характеристики у другого класса. Когда один класс наследуется от другого, наследующий класс известен как подкласс, а класс, от которого идёт наследование, его надклассом. Наследование - это фундаментальное поведение, которое отличает классы от других типов в Swift.

Классы в Swift могут вызывать и получать доступ к методам, свойствам и сабскриптам, принадлежащим их надклассам, а также могут предоставлять свои собственные замещающие версии для этих методов, свойств и сабкриптов для обновления или изменения их поведения. Swift помогает проверить, что перегрузки верны, путём проверки того, что определение перегрузки имеет корреспонидирующее определение в суперклассе.

Классы также могут добавить наблюдатели свойства для унаследованных свойств для того, чтобы быть уведомленными, когда меняется значение свойства. Наблюдатели свойств могут быть добавлены для любого свойства, вне зависимости от того, было ли оно объявлено изначально в качестве хранимого или вычисляемого свойства.

Надкласс и суперкласс являются синонимами, как и подкласс и сабкласс.
Определение базового класса
Любой класс, который ничего не наследует от другого класса, известен как базовый класс.

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

Пример ниже определяет базовый класс с названием Vehicle. Этот базовый класс определяет хранимое свойство с названием currentSpeed и предопределенным значением 0.0 (что выведет тип свойства в Double). Значение свойства currentSpeed используется вычисляемым свойством только-для-чтения типа String с названием description для создания описания транспорта.

Базовый класс Vehicle также определяет метод с названием makeNoise. Этот метод не делает ничего фактически для объекта базового типа Vehicle, но он может быть кастомизирован впоследствии подклассами класса Vehicle:
@{12.3\1\1}
class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // не делает ничего - абстрактный транспорт не может издавать звуков
    }
}
Синтаксис инициализатора
Вы можете создать новый объект типа Vehicle с помощью синтаксиса конструктора, который записывается в качестве имени типа, сопровождаемого пустыми круглыми скобками:
@{12.3\1\2}
let someVehicle = Vehicle()
@{12.3\1\3}
Создав новый объект типа Vehicle, Вы можете получить доступ к его свойству desctiption для распечатки его человокопонятного описания с текущей скоростью транспорта:
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
Класс Vehicle определяет общие характеристики для абстрактного транспорта, но сам по себе он неособо используем. Чтобы сделать его более полезным, Вам нужно заменить его для описания более конкретных типов транспорта.
Подклассинг
Подклассинг - это действие по созданию нового класса на существующем классе. Подкласс наследует характеристики существующего класса, которые Вы затем можете переопределить. Вы также можете добавить новые характеристики в подкласс.

Для определения того, что подкласс имеет надкласс, напишите имя подкласса перед именем суперкласса, разделив их двоеточием:
@{12.3\1\4}
class SomeSuperclass {}

class SomeSubclass: SomeSuperclass {
    // здесь идёт определение подкласса
}
@{12.3\1\5}
Пример определяет подкласс с названием Bicycle с надклассовом Vehicle:
class Bicycle: Vehicle {
    var hasBasket = false
}
Новый класс Bicycle автоматически получает все характеристики класса Vehicle, как например свойства currentSpeed и description и метод makeNoise().

В дополнение к унаследованным характеристикам класс Bicycle определяет новое хранимое свойство, hasBasket, со значением по-умолчанию false (выводящее тип этого свойства в Bool).
@{12.3\1\6}
По умолчанию любой новый экземпляр типа Bicycle, который Вы создаёте, не будет иметь корзины. Вы можете установить свойство hasBasket в значение true для отдельного объекта типа Bicycle после создания объекта:
let bicycle = Bicycle()
bicycle.hasBasket = true
@{12.3\1\7}
Вы также можете изменить унаследованное свойство currentSpeed объекта типа Bicycle и запросить унаследованное свойство description:
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
@{12.3\1\8}
Подклассы могут сами быть унаследованы. Следующий пример создаёт подкласс типа Bicycle для двухместного велосипеда под названием "тандем":
class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}
Tandem наследует все свойства и методы у Bicycle, который в свою очередь наследует все свойства и методы у Vehicle. Подкласс Tandem также добавляет новое хранимое свойство currentNumberOfPassengers со значением по-умолчанию 0.
@{12.3\1\9}
Если Вы создадите объект типа Tandem, Вы можете работать с любым из его новых или унаследованных свойств и запрашивать свойство только-для-чтения description, которое он наследует у Vehicle:
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour
Перезапись
Подкласс может предоставить свою собственную имплементацию метод объекта, метода типа, свойства экземпляра, свойства типа или сабскрипта, которые он наследует у надкласса. Это известно как перезапись.

Чтобы перезаписать характеристику, которая может быть унаследованной, Вы предваряете перезаписывающее определение с помощью ключевого слова override. Когда Вы так делаете, Вы подтверждаете своё намерение предоставить перезапись, а не случайное совпадения определения по ошибке. Перезапись по ошибке может вызвать неожиданное поведение, поэтому любые перезаписи без ключевого слова override будут диагностированы в качестве ошибки в процессе сборки Вашего кода.

Ключевое слово override также указывает компилятору Swift проверить, что Вы перезаписываете надкласс (или одного из его родителей), который имеет объявление, которое совпадает с тем, что Вы предоставляете на перезапись. Эта проверка гарантирует, что Ваше перезаписывающее определение корректно.
Получение доступа к методам суперкласса, его свойствам и сабскриптам
Когда Вы предоставляете перезапись метода, свойства или сабсрикипта для подкласса, то иногда бывает полезным использовать реализацию существующего суперкласса как часть перезаписи. Например, Вы можете изменить поведение существующей реализации или сохранить изменённое значение в существующем унаследованном свойстве.

Там, где это допустимо, Вы можете получить доступ к версии суперкласса метода, свойства или сабскрипта, воспользовавшись префиксом super:
  • Перезаписывющий метод с именем @code(someMethod()) может вызвать версию этого метода у надкласса, вызвав @code(super.someMethod()) внутри реализации перезаписывающего метода.
  • Перезаписыюващее свойство с названием @code(someProperty) может получить доступ к версии этого свойства у суперкласса в качестве @code(super.someProperty) внутри реализации перезаписывающих геттера/ и сеттера.
  • Перезаписывающий сабскрипт для может получить доступ к свой версии суперкласса того же сабскрипта в виде @code(super[someIndex]) из реализации перезаписывающего сабскрипта.
Перезаписывающие методы
Вы можете перезаписать унаследованный метод типа или объекта для предоставления улучшенной или альтернативной реализации этого метода в Вашем подклассе.

Классы также могут использовать ключевое слово class для разрешения подклассам перезаписывать реализации суперклассом этого метода.

Следующий пример определяет новый подкласс у Vehicle с названием Train, который перезаписывает метод makeNoise(), наследуемый классом Train от Vehicle:
@{12.3\1\10}
class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}
@{12.3\1\11}
Если Вы создадите новый объект типа Train и вызовите его метод makeNoise(), Вы сможете увидеть, что версия этого метода подкласса Train вызывается:
let train = Train()
train.makeNoise()
// Выведет "Choo Choo"
Перезапись свойств
Вы можете перезаписать унаследованное свойство объекта или типа для предоставления Ваших собственных геттера или сеттера для этого свойства или для добавления наблюдателей свойств для включения возможности для перезаписываемого свойства наблюдать, когда значение его изменяется.

Для вычисляемых свойств типа для классов Вы можете использовать вместо этого ключевое слово class для того, чтобы позволить подклассу перезаписать реализацию суперкласса.
Перезапись геттеров и сеттеров свойства
Вы можете предоставить произвольный геттер (и сеттер, если это нужно) для перезаписи любого наследуемого свойства, будь это наследуемое свойство реализовано в качестве хранимого или вычисляемого свойства в оригинале. Хранимая или вычисляемая природа унаследованного свойства неизвестна его подклассом: ему известно лишь то, что наследуемое свойство имеет определённое имя и тип. Вы всегда должны указывать и имя, и тип перезаписываемого свойства для того, чтобы позволить компилятору проверить, что Ваша перезапись совпадает со свойством надкласса с тем же именем и типом.

Вы можете сделать унаследованное свойство только-для-чтения свойством чтения-записи, предоставив как геттер, так и сеттер в перезаписи этого свойства подклассом. Вы однако не можете сделать унаследованное свойство чтения-записи свойством только-для-чтения.

Если Вы предоставите сеттер в качестве части перезаписи свойства, то Вы так же должны предоставить геттер для этой перезаписи. Если Вы не хотите изменять значение свойства внутри его перезаписывающего геттера, Вы можете просто передать унаследованное значение вернув super.someProperty из геттера, где someProperty это название перезаписываемого Вами свойства.
@{12.3\1\12}
Следующий пример новый класс с названием Car, который является подклассом Vehicle. Класс Car представляет новое хранимое свойство с названием gear со значением по-умолчанию 1. Класс Car также перезаписыаает свойство description, наследуемое им от Vehicle, для предоставления собственного описания, включающее текущую передачу:
class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}
Перезапись свойства description начинается с вызова super.description, которое возвращает свойство description класса Vehicle. Версия свойства description класса Car добавляет дополнительный текст в конец описания для предоставления информации о текущей передачи.
@{12.3\1\13}
Если Вы создадите объект класса Car и установите его значения gear и currentSpeed, то Вы сможете увидеть, что его свойство description возвращает описание, заданное в классе Car:
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
Перезапись наблюдателей свойств
Вы можете использовать перезапись свойств для добавления наблюдателей свойств в наследуемые свойства. Это позволяет Вам получить уведомления, когда значение унаследованного свойства меняется вне зависимости от того, как данное свойство было изначально реализовано.

Вы не можете добавить наблюдатели для унаследованных константных хранимых свойств или вычисляемых свойств только-для-чтения. Значение этих свойств не могут быть заданы, так что это неправильно предоставлять реализации willSet и didSet в качестве части реализации перезаписи.

Заметьте также, что Вы не можете предоставить одновременно перезаписывающий сеттер и перезаписывающий наблюдатель свойства для одного и того же свойства. Если Вы хотите наблюдать за изменениями в значении свойства и Вы уже предоставили ему сеттер, то Вы можете просто обозревать любые изменения значение из него.
@{12.3\1\14}
Следующий пример определяет новый класс с названием AutomaticCar, который является подклассом Car. Класс AutomaticCar репрезентует машину с автоматической коробкой передач, которая автоматически выбирает подходящую передачу на основе текущей скорости:
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}
@{12.3\1\15}
Каждый раз, когда Вы задаёте свойство currentSpeed объекта AutomaticCar, то наблюдатель свойства didSet устанавливает значения свойства gear в подходящее значении на основании новой скорости. Точнее говоря, наблюдатель свойства выбирает новое, равное новому значению currentSpeed, делённому на 10, округлённому вниз до ближайшего целого, и увеличенного 1. Скорость 35.0 выдают в результате передачу 4:
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
Предотвращение перезаписей
Вы можете лишить метод, свойство или сабскрипт от возможности быть перезаписанными, пометив их в качестве финальных. Сделать это можно, записав модификатор final перед методом, свойством или сабскриптом (как например final var, final func, final class func или final subscript.

Любая попытка перезаписать финальный метод, свойство или сабскрипт в подклассе будет помечена как ошибка времени компиляции. Методы, свойства или сабскрипты, которые Вы добавляете в класс в расширении также могут быть помечены финальными в определении этого расширения.

Вы можете пометить класс как финальный целиком, написав модификатор final перед ключевым словом class в определении класса (final class). Любые попытки унаследовать финальный класс будут помечены как ошибки времени компиляции.