Наследование — Kotlin
Для всех классов в Kotlin родительским суперклассом является класс Any
. Он также является родительским классом для любого класса,
в котором не указан какой-либо другой родительский класс.
class Example // Неявно наследуется от Any
У Any
есть три метода: equals()
, hashCode()
и toString()
. Эти методы определены для всех классов в Kotlin.
По умолчанию все классы в Kotlin имеют статус final, который блокирует возможность наследования.
Чтобы сделать класс наследуемым, его нужно пометить ключевым словом open
.
open class Base // Класс открыт для наследования
Для явного объявления суперкласса мы помещаем его имя за знаком двоеточия в оглавлении класса:
open class Base(p: Int) class Derived(p: Int) : Base(p)
Если у класса есть основной конструктор, базовый тип может (и должен) быть проинициализирован там же, с использованием параметров основного конструктора.
Если у класса нет основного конструктора, тогда каждый последующий дополнительный конструктор должен включать в себя инициализацию базового типа
с помощью ключевого слова super
или давать отсылку на другой конструктор, который это делает.
Примечательно, что любые дополнительные конструкторы могут ссылаться на разные конструкторы базового типа.
class MyView : View { constructor(ctx: Context) : super(ctx) constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) }
Переопределение методов класса
Kotlin требует явно указывать модификаторы и для членов, которые могут быть переопределены, и для самого переопределения.
open class Shape { open fun draw() { /*...*/ } fun fill() { /*...*/ } } class Circle() : Shape() { override fun draw() { /*...*/ } }
Для Circle.draw()
необходим модификатор override
. В случае её отсутствия компилятор выдаст ошибку.
Если у функции типа Shape. fill()
нет модификатора open
, объявление метода с такой же сигнатурой в производном классе невозможно,
с override
или без. Модификатор open
не действует при добавлении к членам final класса (т.е. класса без модификатора open
).
Член класса, помеченный override
, является сам по себе open, т.е. он может быть переопределён в производных классах.
Если вы хотите запретить возможность переопределения такого члена, используйте final
.
open class Rectangle() : Shape() { final override fun draw() { /*...*/ } }
Переопределение свойств класса
Переопределение свойств работает также, как и переопределение методов; все свойства, унаследованные от суперкласса, должны быть помечены ключевым словом override
,
а также должны иметь совместимый тип.
Каждое объявленное свойство может быть переопределено свойством с инициализацией или свойством с
-методом.
open class Shape { open val vertexCount: Int = 0 } class Rectangle : Shape() { override val vertexCount = 4 }
Вы также можете переопределить свойство val
свойством var
, но не наоборот. Это разрешено, поскольку свойство val
объявляет get
-метод, а при переопределении его как var
дополнительно объявляется set
-метод в производном классе.
Обратите внимание, что ключевое слово override
может быть использовано в основном конструкторе класса как часть объявления свойства.
interface Shape { val vertexCount: Int } class Rectangle(override val vertexCount: Int = 4) : Shape // Всегда имеет 4 вершины class Polygon : Shape { override var vertexCount: Int = 0 // Может быть установлено любое количество }
Порядок инициализации производного класса
При создании нового экземпляра класса в первую очередь выполняется инициализация базового класса (этому шагу предшествует только оценка аргументов, передаваемых в конструктор базового класса) и, таким образом, происходит до запуска логики инициализации производного класса.
open class Base(val name: String) { init { println("Инициализация класса Base") } open val size: Int = name. length.also { println("Инициализация свойства size в класса Base: $it") } } class Derived( name: String, val lastName: String, ) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Аргументы, переданные в конструктор класса Base: $it") }) { init { println("Инициализация класса Derived") } override val size: Int = (super.size + lastName.length).also { println("Инициализация свойства size в классе Derived: $it") } } fun main() { println("Построение класса Derived(\"hello\", \"world\")") Derived("hello", "world") }
Это означает, что свойства, объявленные или переопределенные в производном классе, не инициализированы к моменту вызова конструктора базового класса.
Если какое-либо из этих свойств используется в логике инициализации базового класса (прямо или косвенно через другую переопределенную open
реализацию члена класса),
это может привести к некорректному поведению или сбою во время выполнения. Поэтому при разработке базового класса следует избегать использования членов
с ключевым словом open
в конструкторах, инициализации свойств и блоков инициализации (init
).
Вызов функций и свойств суперкласса
Производный класс может вызывать реализацию функций и свойств своего суперкласса, используя ключевое слово super
.
open class Rectangle { open fun draw() { println("Рисование прямоугольника") } val borderColor: String get() = "black" } class FilledRectangle : Rectangle() { override fun draw() { super.draw() println("Заполнение прямоугольника") } val fillColor: String get() = super.borderColor }
Во внутреннем классе доступ к суперклассу внешнего класса осуществляется при помощи ключевого слова super
, за которым следует имя внешнего класса: super@Outer
.
class FilledRectangle: Rectangle() { override fun draw() { val filler = Filler() filler.drawAndFill() } inner class Filler { fun fill() { println("Filling") } fun drawAndFill() { [email protected]() // Вызывает реализацию функции draw() класса Rectangle fill() println("Нарисованный прямоугольник заполнен ${super@FilledRectangle.borderColor} цветом") // Используется реализация get()-метода свойства borderColor в классе } } }
Правила переопределения
В Kotlin правила наследования реализации определены следующим образом: если класс наследует многочисленные реализации одного и того члена от ближайших родительских классов, он должен переопределить этот член и обеспечить свою собственную реализацию (возможно, используя одну из унаследованных).
Для того чтобы отметить конкретный супертип (родительский класс), от которого мы наследуем данную реализацию,
используйте ключевое слово super
. Для задания имени родительского супертипа используются треугольные скобки, например super<Base>
.
open class Rectangle { open fun draw() { /* ... */ } } interface Polygon { fun draw() { /* ... */ } // члены интерфейса открыты ('open') по умолчанию } class Square() : Rectangle(), Polygon { // Компилятор требует, чтобы функция draw() была переопределена: override fun draw() { super<Rectangle>. draw() // вызов Rectangle.draw() super<Polygon>.draw() // вызов Polygon.draw() } }
Это нормально, наследоваться одновременно от Rectangle
и Polygon
,
но так как у каждого из них есть своя реализация функции draw()
,
мы должны переопределить draw()
в Square
и обеспечить нашу собственную реализацию этого метода для устранения получившейся неоднозначности.
Курс Java Syntax PRO Comics
1. Пегас
Давайте поглубже разберем третий принцип ООП — наследование. Это очень интересная тема, которой вы будете пользоваться часто. Программирование, для несведущих, неотличимо от магии. Поэтому начнем с такой интересной аналогии…;
Предположим, что вы – волшебник и хотите создать летающую лошадь. С одной стороны, вы бы могли попробовать наколдовать пегаса. Но т.к. пегасов в природе не существует, это будет очень непросто. Придется очень многое делать самому. Куда проще взять лошадь и приколдовать ей крылья.
В программировании такой процесс называется «наследование». Предположим, вам нужно написать очень сложный класс. Писать с нуля долго, потом еще долго все тестировать и искать ошибки. Зачем идти самым сложным путем? Лучше поискать, а нет ли уже такого класса?
Предположим, вы нашли класс, который своими методами реализует 80% нужной вам функциональности. Что делать с ним дальше? Вы можете просто скопировать его код в свой класс. Но у такого решения есть несколько минусов:
- Найденный класс уже может быть скомпилирован в байт-код, а доступа к его исходному коду у вас нет.
- Исходный код класса есть, но вы работаете в компании, которую могут засудить на пару миллиардов за использование даже 6 строчек чужого кода. А потом она засудит вас.
- Ненужное дублирование большого объема кода. Кроме того, если автор чужого класса найдет в нем ошибку и исправит ее, у вас эта ошибка останется.
Есть решение потоньше, и без необходимости получать легальный доступ к коду оригинального класса. В Java вы можете просто объявить тот класс родителем вашего класса. Это будет эквивалентно тому, что вы добавили код того класса в код своего. В вашем классе появятся все данные и все методы класса-родителя. Например, можно делать так: наследуемся от «лошади», добавляем «крылья» – получаем «пегаса»
2. Общий базовый класс
Наследование можно использовать и для других целей. Допустим, у вас есть десять классов, которые очень похожи, имеют совпадающие данные и методы. Вы можете создать специальный базовый класс, вынести эти данные (и работающие с ними методы) в этот базовый класс и объявить те десять классов его наследниками. Т.е. указать в каждом классе, что у него есть класс-родитель — данный базовый класс.
Также как преимущества абстракции раскрываются только рядом с инкапсуляцией, так и преимущества наследования гораздо сильнее при использовании полиморфизма. Но о нем вы узнаете немного позже. Сегодня же мы рассмотрим несколько примеров использования наследования.
Шахматные фигуры
Предположим, мы пишем программу, которая играет в шахматы с пользователем, а значит, нам понадобятся классы для фигур. Что бы это были за классы?
Очевидный ответ, если вы когда-нибудь играли в шахматы — Король, Ферзь, Слон, Конь, Ладья и Пешка.
Но в самих классах еще нужно было бы хранить информацию по каждой фигуре. Например, координаты x и y, а также ценность фигуры. Ведь некоторые фигуры ценнее других.
Кроме того, фигуры ходят по-разному, а значит и поведение классов будет отличаться. Вот как можно было бы описать их в виде классов:
class King { int x; int y; int worth; void kingMove() { // код, решающий, // как пойдет // король } } | class Queen { int x; int y; int worth; void queenMove() { // код, решающий, // как пойдет // ферзь } } | class Rook { int x; int y; int worth; void rookMove() { // код, решающий, // как пойдет // ладья } } |
class Knight { int x; int y; int worth; void knightMove() { // код, решающий, // как пойдет // конь } } | class Bishop { int x; int y; int worth; void bishopMove() { // код, решающий, // как пойдет // слон } } | class Pawn { int x; int y; int worth; void pawnMove() { // код, решающий, // как пойдет // пешка } } |
Это очень примитивное описание шахматных фигур.
Общий базовый класс
А вот как можно было бы сократить код с помощью наследования. Мы могли бы вынести одинаковые методы и данные в общий класс. Назовем его ChessItem
. Объекты класса ChessItem
не имеет смысла создавать, так как ему не соответствует ни одна шахматная фигура, но от него было бы много пользы:
class King extends ChessItem { void kingMove() { // код, решающий, // как пойдет король } } | class Queen extends ChessItem { void queenMove() { // код, решающий, // как пойдет ферзь } } | class Rook extends ChessItem { void rookMove() { // код, решающий, // как пойдет ладья } } |
class ChessItem { int x; int y; int worth; } | ||
class Knight extends ChessItem { void knightMove() { // код, решающий, // как пойдет конь } } | class Bishop extends ChessItem { void bishopMove() { // код, решающий, // как пойдет слон } } | class Pawn extends ChessItem { void pawnMove() { // код, решающий, // как пойдет пешка } } |
Это отличный способ упростить код похожих объектов. Особенно много преимуществ мы получаем, когда в проекте тысячи различных объектов и сотни классов. Тогда правильно подобранными родительскими (базовыми) классами можно не только существенно упростить логику, но и сократить код в десятки раз.
3. Наследование класса —
extends
Так что же нужно, чтобы унаследовать какой-то класс? Чтобы унаследовать один класс от другого, нужно после объявления нашего класса указать ключевое слово extends
и написать имя родительского класса. Выглядит это обычно примерно так:
class Потомок extends Родитель
Именно такую конструкцию нужно написать при объявлении класса Потомок. Наследоваться, кстати, можно только от одного класса.
На картинке мы видим «корову», унаследованную от «свиньи». «Свинья» унаследована от «курицы», «курица» от «яйца». Только один родитель! Такое наследование не всегда логично. Но если есть только свинья, а очень нужна корова, программист зачастую не может устоять перед желанием сделать «корову» из «свиньи».
В Java нет множественного наследования: нельзя унаследовать класс от двух классов. У каждого класса может быть только один класс-родитель. Если класс-родитель не указан, таковым считается класс Object
.
Хотя в Java есть множественное наследование интерфейсов. Это немного снижает остроту проблемы. Про интерфейсы мы поговорим немного позже, а пока давайте продолжим разбираться с наследованием.
P.S.
Вот вам несколько историй, о том, как часто приходится делать из мухи свинью. И что за это бывает:
- https://habr.com/ru/post/328552/
- https://voron-vp.livejournal.com/42033.html
undefined
Простое наследование
Построй правильную цепочку наследования классов. Женщина должна наследоваться от человека, а человек от землянина.
undefinedНаследование переменных
Правильно унаследуй классы: — машину — от транспортного средства; — электрокар — от машины. Удали дублирующиеся переменные.
undefinedНаследование методов
Правильно унаследуй классы: — человека — от существа; — Java-девелопера — от человека. Удали дублирующие методы.
Наследование Python
❮ Предыдущий Далее ❯
Наследование Python
Наследование позволяет нам определить класс, который наследует все методы и свойства другого класса.
Родительский класс — это наследуемый класс, также называемый базовый класс.
Дочерний класс — это класс, который наследуется от другого класса, также называется производным классом.
Создать родительский класс
Любой класс может быть родительским классом, поэтому синтаксис аналогичен созданию любого другой класс:
Пример
Создайте класс с именем Person
, с имя
и фамилия
свойства,
и метод printname
:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self. firstname,
self.lastname)
# Используйте класс Person для создания объекта, а затем выполнить метод printname:
x = Person(«John», «Doe»)
x.printname()
Попробуйте сами »
Создайте дочерний класс
Чтобы создать класс, наследующий функциональные возможности другого класса, отправьте родительский класс в качестве параметра при создании дочернего элемента class:
Example
Создайте класс с именем Student
, который унаследует свойства
и методы из человек
класс:
класс Студент(человек):
пройти
Примечание: Использовать пропуск
ключевое слово, если вы не хотите добавлять какие-либо другие свойства или методы в
класс.
Теперь класс Student имеет те же свойства и методы, что и класс Person класс.
Пример
Используйте класс Student
для создания объекта,
а затем выполните метод printname
:
x = Student(«Mike», «Olsen»)
x. printname()
Попробуйте сами »
Добавьте функцию __init__()
На данный момент мы создали дочерний класс, который наследует свойства и методы от его родителя.
Мы хотим добавить функцию __init__()
в дочерний класс (вместо ключевого слова pass
).
Примечание: Функция __init__()
вызывается автоматически каждый раз, когда класс используется для создания нового объекта.
Пример
Добавьте функцию __init__()
в Студент
класс:
класс Студент(Человек):
def __init__(self, fname, lname):
#добавить свойства и т. д.
При добавлении функции __init__()
дочерний класс больше не будет наследовать
родительская функция __init__()
.
Примечание: Дочерний __init__()
функция переопределяет наследование родителя __init__()
функция.
Чтобы сохранить наследство родителя __инициализация__()
функцию, добавьте вызов в
родительская функция __init__()
:
Пример
class Student(Person):
def __init__(self, fname, lname):
Person.__init__(self, fname, lname)
Попробуйте сами »
Теперь мы успешно добавили функцию __init__() и сохранили
наследование родительского класса, и мы готовы добавить функциональность в __init__()
функция.
Использовать функцию super()
Python также имеет функцию super()
, которая
заставит дочерний класс наследовать все методы и свойства от его
parent:
Пример
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
Попробуйте сами »
Используя функцию super()
, вы не
должны использовать имя родительского элемента, он автоматически унаследует
методы и свойства от своего родителя.
Добавить свойства
Пример
Добавьте свойство с именем выпускной год
в Ученик
класс:
класс Ученик(Человек):
def __init__(self, fname, lname):
super().__init__(fname, lname)
self.graduationyear
= 2019
Попробуйте сами »
В приведенном ниже примере год 2019
должен быть переменной и передаваться в Класс Student
при создании объектов Student.
Для этого добавьте еще один параметр в функцию __init__():
Пример
Добавьте параметр года
и передайте правильный
год создания объектов:
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear
= год
x = студент («Майк», «Олсен», 2019)
Попробуйте сами »
Добавить методы
Пример
Добавьте метод с именем welcome
в Студент
класс:
class Student(Person):
def __init__(self, fname, lname, year):
super().