Tehnografi.com - Технологические новости, обзоры и советы

Наследование и подклассы, объясненные на Swift

Следующая статья поможет вам: Наследование и подклассы, объясненные на Swift

Вернуться в блог

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

Вот что мы обсудим:

  • Что такое подклассы и наследование и зачем они вам нужны?
  • Переопределение функций с помощью override и super
  • Одноколесный велосипед – это велосипед, это транспортное средство…
  • Почему вы можете и не можете переопределять свойства
  • Альтернативы созданию подклассов, например расширения протоколов.
  • Когда разумно использовать наследование (а когда нет)

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

Точно так же, как вы унаследовали гены от своей матери и даже дедушки, классы Swift могут наследовать вещи от суперкласса.

Давайте посмотрим на пример:

класс Транспортное средство
{
переменная скорость: Int = 0

функция описания() {
print(“движение со скоростью \(скорость) км/ч”)
}
}

В приведенном выше коде мы определили класс под названием Vehicle. Он имеет одно свойство Speed ​​типа Int со значением по умолчанию, равным нулю. В классе также есть функция описать(), которая выводит некоторый текст.

Вот как мы можем использовать этот класс Vehicle:

пусть ракета = Транспортное средство()
ракета.скорость = 1080000000
ракета.описать()
// Выход: движение со скоростью 1080000000 км/ч

Все идет нормально! Это экземпляр Vehicle, назначенный ракете, и мы настраиваем свойство скорости и вызываем функцию описания().

Давайте создадим подкласс следующим образом:

класс велосипеда: Транспортное средство {
вар цвет: String = «»
}

В приведенном выше коде мы создали класс Bike. Он является подклассом класса Vehicle. Вы можете видеть это на основе подкласса класса: синтаксис суперкласса { в определении класса.

Здесь существует иерархия:

  • Подкласс: Подкласс является подклассом другого класса и наследует все его свойства и функции. Это Байк.
  • Суперкласс: «Суперкласс» — это термин для класса, подкласс которого вы создаете. Транспортное средство является суперклассом Мотоцикла.

Класс Bike теперь унаследовал все функции и свойства класса Vehicle. Также добавлено собственное свойство цвета типа String. Это свойство цвета доступно только для экземпляров класса Bike.

Проверь это:

пусть мойВелосипед = Велосипед()
myBike.speed = 15
myBike.color = «красный»
myBike.describe()
// двигаемся со скоростью 15 км/ч

В приведенном выше коде мы создали экземпляр класса Bike. Вы можете видеть, что мы устанавливаем свойство скорости, например, на 15 км/ч. Это свойство скорости унаследовано от Vehicle. То же самое касается функции описать(). Вот что такое подклассы!

Вы можете создать подкласс только одного суперкласса, поэтому один класс не может наследовать более чем от одного суперкласса. Однако вы можете создать подкласс дальше «вниз». Проверь это:

класс Одноколесный велосипед: Велосипед {
вар электрический = ложь
}

пусть fastUnicycle = Unicycle()
fastUnicycle.speed = 999999
fastUnicycle.color = «синий»
fastUnicycle.electric = правда
fastUnicycle.describe()
// двигаемся со скоростью 999999 км/ч

В приведенном выше коде у вас есть класс Unicycle, который является подклассом Bike. Поскольку Bike является подклассом Vehicle, класс Unicycle теперь наследует все от обоих классов.

Сформировалась иерархия: Транспортное средство → Велосипед → Одноколесный велосипед. Таким образом, мы можем сказать, что одноколесный велосипед – это велосипед – это транспортное средство. Вы даже можете проверить эту связь с помощью ключевого слова «is» в Swift. Так:

если fastUnicycle — это Vehicle {
print(“fastUnicycle — это транспортное средство”)
}
// Вывод: fastUnicycle — это транспортное средство

Потрясающий!

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

Переопределение функций

Но что, если вы хотите изменить то, что уже есть? Вот здесь-то и появляется переопределение. Посмотрите это:

класс велосипеда: Транспортное средство {
вар цвет: String = «»

переопределить функцию описать() {
print(“\(цвет) велосипед движется со скоростью \(скорость) км/ч”)
}
}

В приведенном выше коде мы переопределили функцию описания(). Это эффективно заменяет функцию новой реализацией.

Чтобы переопределить функцию, вам нужно сделать 2 вещи:

  1. Точно повторите сигнатуру функции, включая параметры и тип возвращаемого значения.
  2. Добавьте к сигнатуре функции ключевое слово override.

Зачем нужно повторять объявление функции? Если вы переопределяете функцию, вы не можете изменить имя функции, ее параметры или тип возвращаемого значения. Они должны оставаться точно такими же, как функция, которую вы переопределяете.

Если мы запустим тот же код, что и раньше, вы увидите, что результат будет другим:

пусть мойВелосипед = Велосипед()
myBike.speed = 15
myBike.color = «красный»
myBike.describe()
// Выход: красный велосипед движется со скоростью 15 км/ч.

Новая реализация функции define() теперь использует свойство цвета Bike, поскольку мы его переопределили. Аккуратный!

Что делать, если вам нужно получить доступ к «предыдущей» реализации переопределяемой функции; тот, что из суперкласса? Вы можете получить доступ к этой функции, используя ключевое слово super. Вот пример:

класс Транспортное средство {

функция описания() -> Строка {
вернуть «движение со скоростью \(скорость) км/ч»
}
}

класс велосипеда: Транспортное средство {

переопределить функцию описания() -> String {
вернуть «\(цвет) велосипед» + super.describe()
}
}

В приведенном выше коде переопределенная функция define() в классе Bike добавляет к возвращаемому значению цвет велосипеда. Таким образом, результат для любого красного велосипеда будет выглядеть примерно так: «красный велосипед движется со скоростью 15 км/ч».

Это работает с помощью ключевого слова super. Код super.describe() вызовет реализацию суперкласса функции описать(), как если бы вы ее не переопределили. Это работает только внутри переопределяемой вами функции описания().

Если вы переопределяете методы получения и установки свойства, вы можете получить доступ к значению суперкласса свойства с помощью super.property. Подробнее об этом ниже!

Почему вы не можете переопределить свойства

Можете ли вы также переопределить свойства? Да и нет. Проверь это:

класс Транспортное средство
{
переменная скорость: Int = 0

}

класс велосипеда: Транспортное средство {
переопределить переменную скорость: Double = 0,0
}

В приведенном выше коде мы пытаемся переопределить свойство скорости в подклассе Bike, изменив его тип с Int на Double. Это не сработает! Почему нет?

Во-первых, переопределяя, вы можете предоставить только собственную реализацию. Вы можете предоставить собственное «содержимое» функции или свойства, но не можете изменить его определение. Вы не можете изменить типы и/или параметры функции или свойства.

Есть вторая причина. Помните, мы обсуждали, что одноколесный велосипед — это велосипед, это транспортное средство? Таким образом, мы можем предоставить экземпляр типа Unicycle, когда требуется тип транспортного средства, поскольку они связаны. Проверь это:

продолжительность функции (для транспортного средства: Транспортное средство, расстояние: Int) {
пусть продолжительность = расстояние / скорость транспортного средства
print(“автомобилю потребуется \(duration) часов, чтобы проехать \(расстояние) км”)
}

Эта функция вычисляет, сколько времени потребуется транспортному средству, чтобы преодолеть заданное расстояние. Видите параметры функции? У нас есть один тип «Транспортное средство», а другой — расстояние.

Вот как вы можете его использовать:

пусть мойВелосипед = Велосипед()
myBike.speed = 15

продолжительность(для: myBike, расстояние: 30)
// Выход: транспортному средству потребуется 2 часа, чтобы проехать 30 км.

Пока все работает. Длительность функции (for:distance:) зависит от того, чтобы значения скорости и расстояния имели тип Int, потому что именно благодаря этому вычисление расстояния/скорости работает нормально. Оба имеют один и тот же тип.

Здесь также стоит отметить, что можно предоставить экземпляр типа Bike для параметра, тип которого — Vehicle. Это связано с тем, что Bike является подклассом Vehicle, поэтому мы можем логически предположить, что «Велосипед — это Транспортное средство».

Что произойдет, если вы переопределите свойство скорости, как мы делали раньше?

класс велосипеда: Транспортное средство {
переопределить переменную скорость: Double = 0,0
}

Внезапно расчетное расстояние/транспортное средство.скорость меняет тип: Int, разделенный на Double. Это не работает! Оператор деления / четко указывает, что ему нужно разделить 2 значения одного типа.

Конечно, это могло бы сработать, если бы преобразовать Int в Double. Но это не главное! Ключом к переопределению является то, что вы не можете изменять типы в коде, которые зависят от того, какое свойство или функция будет работать.

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

Переопределение наблюдателей, геттеров, сеттеров

Хотя что можно переопределить? Что ж, поскольку принцип гласит, что вы можете переопределить реализацию, вы можете изменить наблюдателей, геттеры и сеттеры свойства.

Проверь это:

класс велосипеда: Транспортное средство {
переопределить переменную скорость: Int {
DidSet {
print(“скорость изменена с \(oldValue) на \(speed)”)
}
}

}

пусть мойВелосипед = Велосипед()
myBike.speed = 15
// Вывод: изменена скорость с 0 на 15

В приведенном выше коде мы добавили наблюдатель свойства под названием DidSet к свойству скорости. По сути, это небольшой код, который выполняется при изменении значения свойства Speed. Внутри DidSet у вас есть доступ к самому свойству скорости и к его предыдущему значению, называемому oldValue.

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

Вы можете переопределить свойства суперкласса, а точнее, наблюдателей свойств willSet и DidSet, а также методы получения и установки get и set. Как и в случае с функциями, вы можете использовать super для доступа к значению свойства суперкласса. Вы можете узнать больше о них в этом уроке: Объяснение свойств в Swift.

Когда следует использовать наследование? Мы уже немного затронули эту тему, и основная причина использования наследования — повторное использование кода.

Допустим, вы на самом деле создаете приложение, в котором задействованы транспортные средства, автомобили, велосипеды, одноколесные велосипеды, мотоциклы и т. д. Между этими вещами явно существует взаимосвязь, поэтому имеет смысл создать иерархию. Что-то вроде этого:

  • Транспортное средство
  • Машина
  • Гоночный автомобиль
  • СемейныйАвтомобиль
  • Велосипед
  • Велосипед
  • Мотоцикл
  • Одноколесный велосипед

Вы можете себе представить, что RaceCar и FamilyCar имеют общие характеристики, унаследованные от Car. У моноцикла и гоночного автомобиля есть кое-что общее, потому что они оба являются транспортными средствами.

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

Распространенным вариантом использования является контроллер представления. Фактически, каждый раз, когда вы размещаете контроллер представления на экране, вы создаете подкласс существующего класса UIViewController! Только так это работает.

С контроллерами представления вы наследуете функциональность контроллера представления. Вы также предоставляете собственную реализацию viewDidLoad() и добавляете несколько свойств, функций и т. д.

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

Создание подклассов имеет 3 важных недостатка:

  1. Вы также наследуете функции и свойства, которые вам не нужны.
  2. Это создает жесткую иерархию между классами.
  3. Это бесполезно, когда типы связаны, но не потомки.

(Кроме того, вы не можете создавать подклассы со структурами.)

Возьмем, к примеру, современный фреймворк, такой как SwiftUI. Если вы создаете пользовательские интерфейсы (UI) с помощью SwiftUI, вам не нужно ничего создавать подклассы. Вы составляете представления из уже существующих компонентов пользовательского интерфейса, таких как VStack и Text.

SwiftUI делает это замечательным способом, который включает в себя 3 альтернативы созданию подклассов:

  1. Протоколы. В SwiftUI представление — это протокол. Таким образом, у него есть свойства, такие как тело, которые вам необходимо предоставить. Платформа SwiftUI тогда опирается на тот факт, что все представления, соответствующие протоколу View, имеют свойство body, и это содержимое, которое оно выводит на экран.
  2. Расширения. В отличие от создания подклассов, расширение дополняет существующий тип новыми функциями. Это позволяет вам дополнять существующие типы и повторно использовать их без создания подклассов. С помощью расширения протокола вы можете привести существующие типы в соответствие с протоколом, что поможет вам создавать составной код.
  3. Дженерики. Универсальный тип — это гибкий тип или функция. Например, универсальная функция может умножать целые и двойные числа, не объявляя одну и ту же функцию дважды. Это позволяет вам совместно использовать/повторно использовать функциональность между большим количеством типов без необходимости создавать подклассы или повторять код. Кроме того, непрозрачные типы делают конкретный тип зависимым от его реализации, что позволяет создавать еще более гибкий код.

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

Наследование — это логичный ответ на вопрос: «Как я могу быть более ленивым программистом?» И в этом случае лень – это хорошо! Когда вы повторно используете больше кода, вам приходится писать меньше кода, а это означает, что его легче поддерживать, расширять и исправлять. Это, конечно, не вся история. В этом уроке мы обсудили, как, когда и почему разумно использовать наследование и создание подклассов в вашем коде Swift.

Вот во что мы вошли:

  • Что такое подклассы и наследование и зачем они вам нужны?
  • Переопределение функций с помощью override и super
  • Одноколесный велосипед – это велосипед, это транспортное средство…
  • Почему вы можете и не можете переопределять свойства
  • Альтернативы созданию подклассов, например расширения протоколов.
  • Когда разумно использовать наследование (а когда нет)