Абстрактные классы
В программировании абстрактные классы – это такие классы, от которых нельзя создавать объекты. Они используются лишь в качестве суперкласса, в который вынесено все общее из дочерних классов.
Например, в программе есть различные классы юнитов – пехотинцы, всадники, герои. Их общие свойства и методы можно вынести в один общий класс "юнит". Поскольку в программе не может быть просто юнита, такой класс имеет смысл сделать абстрактным.
В Kotlin абстрактные классы имеют модификатор abstract
вместо open
, то есть абстрактные классы всегда открыты для наследования, иначе в них не было бы смысла. Сделаем наш класс NumInc абстрактным:
abstract class NumInc(n: Int, s: Int) { var number = n var step = s fun inc() {number += step} fun dec() {number -= step} }
Теперь мы не сможем создать объект от NumInc, хотя переменную такого типа создать можем. Ее можно связать с объектами дочерних от NumInc неабстрактных классов.
val a = NumMult(3,4) val b: NumInc = NumDouble(2, 2)
Здесь переменная a будет иметь тип дочернего класса, b хоть и связана с объектом дочернего класса, будет иметь тип абстрактного родительского класса. Следующее выражение недопустимо:
val c = NumInc(1, 4)
Оно содержит ошибку, так как мы пытаемся создать объект абстрактного класса.
Если класс NumInc определен так, как представлено выше, в дочерних классах мы не можем переопределять его свойства и методы, только добавлять новые. Чтобы иметь возможность переопределения, их по-прежнему надо делать открытыми.
abstract class NumInc(n: Int, s: Int) { var number = n var step = s open fun inc() {number += step} open fun dec() {number -= step} }
class NumDouble(n: Int, s: Int): NumInc(n, s) { override fun inc() { super.inc() super.inc() } }
Однако кроме такого варианта, методы можно объявлять абстрактными. В этом случае они не должны содержать тела, то есть должны быть без реализации. В свою очередь дочерний класс обязан реализовать эти методы. То есть абстрактный метод родителя в дочернем классе всегда должен быть переопределен.
abstract class NumInc(n: Int, s: Int) { var number = n var step = s abstract fun inc() abstract fun dec() }
class NumDouble(n: Int, s: Int): NumInc(n, s) { override fun inc() { number += 2 * step } override fun dec() { number -= 2 * step } }
В другом дочернем классе реализация функций inc()
и dec()
может быть совсем другой.
class NumMult(n: Int, s: Int, q: Int): NumInc(n, s) { val coefficient = q override fun inc() { number += step * coefficient } override fun dec() { number -= step * coefficient } }
Одинаковыми являются лишь параметры (в данном случае их нет) и тип возвращаемого значения. Все это определено в родительском абстрактном классе.
Обратим внимание еще раз. Когда мы делаем методы абстрактного класса просто открытыми или оставляем закрытыми, то можем их не переопределять в дочерних. Хотя при необходимости можем и переопределить. Если метод абстрактный, то не переопределить его нельзя. Это будет ошибкой. Мы обязаны создавать его переопределение в каждом дочернем классе.
В чем выгода от этого? Мы могли бы вообще не описывать эти абстрактные методы в абстрактном классе и реализовывать только в дочерних при необходимости. Выгода – в одинаковой сигнатуре дочерних классов. Гарантируется, что у всех них есть что-то общее – одинаковые методы, что позволит выполнять групповую обработку объектов, созданных от разных классов.
В IntelliJ IDEA, когда вы создаете класс и хотите (или это требуется в случае абстрактных) переопределить свойства и методы родительского класса, можно нажать Ctrl + O, появится окно, где следует выбрать то, что вам требуется. IDEA сама сформирует заголовок.
Абстрактными могут быть не только функции-члены, но и свойства. Однако в этом случае их нельзя инициировать значениями. Пример полностью абстрактного класса:
abstract class NumInc { abstract var number: Int abstract var step: Int abstract fun inc() abstract fun dec() }
Обратите внимание, что у класса нет собственного конструктора. Пример его дочернего класса:
class NumMult(n: Int, s: Int, q: Int): NumInc() { override var number = n override var step = s val coefficient = q override fun inc() { number += step * coefficient } override fun dec() { number -= step * coefficient } }
Практическая работа:
-
Проверьте, можно ли в неабстрактном классе определить абстрактный метод. Объясните результат.
-
Проверьте, может ли абстрактный класс сам быть наследником другого класса. Обязан ли последний быть также абстрактным. Результат объясните.
PDF-версия курса с ответами к практическим работам