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


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

Класс-наследник еще называют подклассом, а класс, от которого наследуется функционал, - базовым классом или суперклассом.

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

Общий синтаксис наследования классов выглядит следующим образом:

1
2
class SubClass: SuperClass{
}
Для примера наследования рассмотрим простейшую ситуацию. Пусть у нас есть класс человека и класс служащего:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class User{

var name: String
var surname: String

init(name: String, surname: String){

self.name = name
self.surname = surname
}

func getFullInfo() -> String{

return "\(self.name) \(self.surname)"
}
}

class Employee : User{

var company: String
init(name: String, surname: String, company: String){

self.company = company
super.init(name: name, surname: surname)
}
}
Здесь класс Emplyee наследуется от класса User. Ведь класс сотрудника по сути будет повторять функционал класса человека, так как каждый сотрудник имеет имя и фамилию. И наследование в данном случае помогает избежать ненужного повторения при определении свойств и методов.

А после создания объекта Employee мы можем через него обращаться к свойствам и методам базового класса User:

1
2
3
4
var emp: Employee = Employee(name: "Steve", surname: "Jobs", company:"Apple")
var emplInfo = emp.getFullInfo() // Steve Jobs
emp.name = "Tim"
emp.surname = "Cook"
Ключевое слово super
Ключевое слово super позволяет обращаться из подкласса к свойствам и методам базового класса. В выше приведенном примере ключевое слово super используется для обращения к инициализатору базового класса ля передачи ему значений.

Переопределение методов
Подкласс может перенимать полностью функционал базового класса, а может и переопределять его. Для переопределения используется ключевое слово override.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class User{

var name: String
var surname: String

init(name: String, surname: String){

self.name = name
self.surname = surname
}

func getFullInfo() -> String{

return "\(self.name) \(self.surname)"
}
}

class Employee : User{

var company: String
init(name: String, surname: String, company: String){

self.company = company
super.init(name: name, surname: surname)
}

override func getFullInfo() -> String{

return "\(self.name) \(self.surname) - \(self.company)"
}
}

var emp: Employee = Employee(name: "Steve", surname: "Jobs", company:"Apple")
print(emp.getFullInfo()) // Steve Jobs - Apple
В данном случае мы переопределяем метод getFullInfo(). Теперь кроме имени и фамилии он также возвращает данные о компании, в которой сотрудник работает. И всегда, когда мы будем вызывать метод getFullInfo() у объекта Employee, будет срабатывать именно переопределенная версия метода.

Используя ключевое super, мы можем переопределить метод по-другому:

1
2
3
4
override func getFullInfo() -> String{

return "\(super.getFullInfo) - \(self.company)"
}
или

1
2
3
4
override func getFullInfo() -> String{

return super.getFullInfo() + " - \(self.company)"
}
Переопределение свойств
Подобным образом мы можем переопределять свойства:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class User{

var name: String
var surname: String

init(name: String, surname: String){

self.name = name
self.surname = surname
}

var fullInfo: String{

return "\(self.name) \(self.surname)"
}
}

class Employee : User{

var company: String
init(name: String, surname: String, company: String){

self.company = company
super.init(name: name, surname: surname)
}

override var fullInfo: String{

return super.fullInfo + " - \(self.company)"
}
}

var emp: Employee = Employee(name: "Steve", surname: "Jobs", company:"Apple")
print(emp.fullInfo) // Steve Jobs - Apple
В данном случае переопределяется свойство fullInfo.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class User{

var name: String
var surname: String

init(name: String, surname: String){

self.name = name
self.surname = surname
}

var fullInfo: String{

return "\(self.name) \(self.surname)"
}
}

class Employee : User{

var company: String
override init(name: String, surname: String){

self.company = "Unknown"
super.init(name: "Mr." + name, surname: surname)
}

init(name: String, surname: String, company: String){

self.company = company
super.init(name: name, surname: surname)
}

override var fullInfo: String{

return super.fullInfo + " - \(self.company)"
}
}

var emp: Employee = Employee(name: "Tim", surname: "Cook")
print(emp.fullInfo) // Mr. Tim Cook - Unknown
Обязательные инициализаторы
В предыдущем примере нам было необязательно переопределять инициализатор класса User в классе Employee. Однако с помощью ключевого слова required мы можем отметить этот инициализатор как обязательный для переопределения в подклассах:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class User{

var name: String
var surname: String

required init(name: String, surname: String){

self.name = name
self.surname = surname
}
}

class Employee : User{

var company: String
required init(name: String, surname: String){

self.company = "Unknown"
super.init(name: "Mr." + name, surname: surname)
}

init(name: String, surname: String, company: String){

self.company = company
super.init(name: name, surname: surname)
}
}
Запрет переопределения
С помощью ключевого слова final мы можем запретить переопределение свойств, методов, сабскриптов в производном классе:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User{

var name: String
var surname: String

init(name: String, surname: String){

self.name = name
self.surname = surname
}

final var fullInfo: String{

return "\(self.name) \(self.surname)"
}
}
Теперь свойство fullInfo нельзя будет переопределить в производном классе.

Более того мы можем вообще запретить наследование класса, поставив перед его определением ключевое слово final:

1
2
3
4
final class User{

//.............
}