Урок 2.5 Классы и наследование

Теперь вы узнали о структурах как о способе компиляции данных и функциональности в тип. Многие языки программирования также поддерживают другую функцию, называемую классами, которые выполняют аналогичную функциональность. В особых случаях классы могут (и должны) использоваться вместо структур.

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


Что Вы Узнаете

  • Разница между структурой и классом
  • Как определить класс
  • Понятие и важность наследования
  • Как написать класс, который наследуется от другого класса
  • Как использовать класс для управления сложными состояниями в приложении

Словарь


Связанные Ресурсы

Руководство по языку программирования Swift: Классы и структуры
Руководство по языку программирования Swift: Наследование

 

Классы очень похожи на структуры. Помимо прочего, оба могут определять свойства для хранения значений, определять методы для обеспечения функциональности и определять инициализаторы для настройки их начального состояния. Синтаксис для выполнения этих действий почти всегда идентичен.
Вы определяете класс, используя ключевое слово class вместе с уникальным именем. Затем вы определяете свойства как часть от class, перечисляя объявления констант или переменных с соответствующей аннотацией типа. Как и в случае со структурами, лучше всего писать имена типов с заглавной буквы и использовать строчные буквы для названий свойств:

 

class Person {
  let name: String
 
  init(name: String) {
    self.name = name
  }
}

 

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

 

class Person {
  let name: String
 
  init(name: String) {
    self.name = name
  }
 x
  func sayHello() {
    print(”Hello, there!”)
  }
 }
 
let person = Person(name: “Jasmine”)
print(person.name)
person.sayHello()


Console Output:
Jasmine
Hello, there!

 

Вы видели почти идентичный пример в предыдущем уроке о структурах. Но чем классы отличаются от структур

Наследование

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

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

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

Определение базового класса

Класс, который не наследуется от суперкласса, называется базовым классом. Все классы, которые вы видели до сих пор, являются базовыми классами.

Вот пример базового класса для объекта Vehicle:

 

class Vehicle {
    var currentSpeed = 0.0
 
    var description: String {
        “traveling at \(currentSpeed) miles per hour”
    }
 
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn’t necessarily make a noise
    }
}
 
let someVehicle = Vehicle()
print(”Vehicle: \(someVehicle.description)”)


Console Output:
Vehicle: traveling at 0.0 miles per hour

 

Класс транспортного средства имеет свойство под названием currentSpeed со значением по умолчанию 0.0. Свойство currentSpeed используется в вычисляемой переменной описания, которая возвращает удобочитаемое описание транспортного средства. В классе также есть метод, называемый makeNoise(). Этот метод на самом деле ничего не делает для базового экземпляра транспортного средства, но позже будет настроен подклассами.

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

Создайте подкласс

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

Чтобы определить новый тип, наследуемый от суперкласса, напишите имя типа перед именем суперкласса, разделенное двоеточием:

 

class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}
 
The following example defines a subclass called Bicycle, with a superclass of Vehicle:
 
class Bicycle: Vehicle {
    var hasBasket = false
}

 

Новый класс Bicycle автоматически наследует все свойства и методы Vehicle, такие как его свойства currentSpeed и description, а также его метод makeNoise()

В дополнение к унаследованным свойствам и методам класс Bicycle определяет новое логическое свойство hasBasket. По умолчанию новый экземпляр Bicycle не будет иметь корзины. Вы можете установить свойству hasBasket значение true для конкретного экземпляра Bicycle после его создания или в пользовательском инициализаторе для этого типа.

 

let bicycle = Bicycle()
bicycle.hasBasket = true
 

Как и следовало ожидать, вы также можете обновить свойство currentSpeed, что повлияет на описание экземпляра.
 

bicycle.currentSpeed = 15.0
print(”Bicycle: \(bicycle.description)”)


Console Output:
Bicycle: traveling at 15.0 miles per hour
 

Подклассы сами по себе могут быть подклассами. Например, вы можете создать класс, представляющий двухместный  TandemBike.

 
class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

 

Tandem наследует все свойства и методы от Bicycle, который, в свою очередь, наследует все свойства и методы от Vehicle. Подкласс Tandom также добавляет новое свойство currentNumberOfPassengers со значением по умолчанию 0. Если вы создадите новый экземпляр Tandem, у вас будет доступ ко всем унаследованным свойствам и методам.

 

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print(”Tandem: \(tandem.description)”)


Console Output:
Tandem: traveling at 22.0 miles per hour

 

Переопределение методов и свойств

Одним из преимуществ подклассов является то, что каждый подкласс может предоставить свою собственную пользовательскую реализацию свойства или метода. Чтобы переопределить характеристику, которая в противном случае была бы унаследована, например, при написании новой реализации для функции, вы добавляете к своему новому определению ключевое слово override.

В следующем примере определяется новый класс Train, который переопределяет метод makeNoise(), который Train наследует от Vehicle:

 

class Train: Vehicle {
    override func makeNoise() {
        print(”Choo Choo!”)
    }
}
 
let train = Train()
train.makeNoise()


Console Output:
Choo Choo!

 

Вы также можете переопределить свойства, предоставив средство получения или блок кода, который возвращает значение, например вычисляемое свойство. В следующем примере определяется новый класс под названием Car, который является подклассом Vehicle. Новый класс имеет новое свойство gear со значением по умолчанию 1. Класс Car также переопределяет свойство description, чтобы предоставить пользовательское описание, включающее текущую передачу:

 

class Car: Vehicle {
    var gear = 1
    override var description: String {
        super.description + “ in gear \(gear)”
    }
}

 

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

 

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(”Car: \(car.description)”)


Console Output:
Car: traveling at 25.0 miles per hour in gear 3

 

Переопределить Инициализатор

Предположим, у вас есть класс Person со свойством name. Инициализатор устанавливает name в соответствии с указанным параметром.

 

class Person {
  let name: String
 
  init(name: String) {
    self.name = name
  }
}

 

Теперь представьте, что вы хотите создать Student, который является подклассом Person. Каждый Student включает в себя дополнительное свойство, favoriteSubject.

 

class Student: Person {
  var favoriteSubject: String
}

 

Если вы попытаетесь скомпилировать этот код, Student завершится неудачей, потому что вы не предоставили инициализатор, который устанавливает для свойства favoriteSubject начальное значение. Поскольку суперкласс Person уже выполняет работу по инициализации свойства name, инициализатор Student может вызвать инициализатор суперкласса с помощью super.init(). Вы должны вызвать этот инициализатор после того, как вы указали значения для любых свойств, добавленных в вашем подклассе.

 

class Student: Person {
  var favoriteSubject: String
 
  init(name: String, favoriteSubject: String) {
    self.favoriteSubject = favoriteSubject
    super.init(name: name)
  }
}

 

Вызывая инициализатор суперкласса, класс Student не должен дублировать работу, которая уже была написана в инициализаторе Person

 

Ссылки

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

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

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

 

class Person {
  let name: String
  var age: Int
 
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}
 
var jack = Person(name: “Jack”, age: 24)
var myFriend = jack
 
jack.age += 1
 
print(jack.age)
print(myFriend.age)


Console Output:
25
25

 

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

 

class Person {
  let name: String
  var age: Int
}
 
var jack = Person(name: “Jack”, age: 24)
var myFriend = jack
 
jack.age += 1
 
print(jack.age)
print(myFriend.age)


Console Output:
25
24

 

Инициализаторы по элементам

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

Класс или Структура?

Поскольку классы и структуры настолько похожи, может быть трудно понять, когда использовать классы, а когда использовать структуры для строительных блоков вашей программы.

Как основное правило, вы должны запускать новые типы как структуры до тех пор, пока вам не понадобится одна из функций, предоставляемых классами.

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

Работа с фреймворками, использующими классы

Вы часто будете работать с ресурсами, которые не писали, такими как фреймворки, такие как Foundation или UIView. В таких ситуациях вы можете начать с базового класса, а затем создать подкласс, чтобы добавить свою собственную специфическую функциональность. Например, когда вы работаете с UIKit и хотите создать пользовательский вид, вы создаете подкласс UIView.

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

Стабильная Идентичность

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

Рассмотрим подкласс UITableViewCell MessageCell, который представляет строку в табличном представлении. Ячейка предназначена для отображения информации о сообщении электронной почты. Как вы увидите, доступ к каждому экземпляру MessageCell будет осуществляться во многих местах кода.

 

class MessageCell: UITableViewCell {
 
  func update(message: Message) {
    // Update `UITableViewCell` properties with information about the message 
    textLabel.text = message.subject
    detailTextLabel.text = message.previewText
  }
}

 

Когда табличное представление готовится к отображению ячеек, оно вызывает функцию cellForRow(at: indexPath), чтобы запросить ячейку, которую оно должно отображать в каждой позиции списка. Все ячейки для табличного представления инициализируются (и помещаются в память) во время вызова этой функции.

 

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
 
  let cell = MessageCell(style: .default, reuseIdentifier: “MessageCell”)
  cell.update(message: message)
 
  // Returns the cell to the table view that called this method to request it
  return cell
}

 

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

 

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  // Perform any operations you want to take on the cell here
}

 

Здесь есть какой-то незнакомый синтаксис, о котором вы узнаете больше позже. А пока сосредоточьтесь на том факте, что функция вызывается для каждой ячейки, которая вот-вот появится в табличном представлении. Также обратите внимание, что функция передает три параметра: ссылку на TableView, которая уже существует; cell, которая уже существует, но вот-вот будет отображена; и нечто, называемое indexPath.

Поскольку TableView и cell являются классами, параметры являются ссылками на TableView и cell в памяти. Если вы обновляете параметр ячейки, вы обновляете ту же ячейку, которая была инициализирована в функции cellForRow, ту же ячейку, которая будет отображаться.

Рассмотрим, как класс работает иначе, чем структура. Если бы cell была структурой или типом значения, параметр был бы новой копией ячейки, которая должна быть отображена. Таким образом, если вы обновили cell, вы не увидите своих изменений, потому что вы обновили копию, а не ячейку, которая должна быть отображена.

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

Лабораторная работа

Откройте и завершите упражнения в Lab—Classes.playground.

 


Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.