Вопрос для начинающих - Наследование - почему не используется мой параметр конструктора возраста? - PullRequest
0 голосов
/ 06 ноября 2018

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

Персональный класс

open class Person(open var firstname: String, open var surname: String,
          open var age: Int) {

val thisyear: Int = Calendar.getInstance().get(Calendar.YEAR)

val dob = thisyear - age

fun printperson() {

    println("$firstname $surname was born in $dob")

}
}

Студенческий класс

class Student(override var firstname: String, override var surname: 
                String, override var age: Int, val studentID: Int):    
                    Person(firstname, surname, age) {

fun returnDetails() {

    println("Hello $firstname, your Student ID is: $studentID")
}
}

Главная

fun main(args: Array<String>) {

val studentMike = Student(firstname = "Mike", 
                  surname = "Stand", age = 67, studentID = 8899)

studentMike.printperson()
studentMike.returnDetails()

val personBill = Person(firstname = "Bill", surname = "Hook", age = 34)
personBill.printperson()

}

выход

Mike Stand was born in 2018
Hello Mike, your Student ID is: 8899
Bill Hook was born in 1984

Как вы можете видеть, Билл непосредственно использовал метод в классе Person, тогда как Майк был косвенным вызовом, параметр age должен был быть унаследован от класса Person через класс Student ...

Глядя на официальные документы Kotlin, кажется, что проблема связана с " Порядком инициализации производного класса ", но я не могу понять, как это исправить.

Спасибо за помощь,

PS извиняюсь за неточную терминологию. Всегда рада указателям сделать лучше.

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

То, что сказал Умберто Коззи, правильно, это связано с тем, что вы ссылаетесь на открытое значение в конструкторе. Если вы пройдете по коду, вы увидите, что последовательность событий:

  1. Вы вызываете конструктор Student, передавая значение для age.
  2. Первое, что делает конструктор ( до создания объекта Student), - это вызов конструктора суперкласса.
  3. Во время создания суперкласса (Person) вы попали в эту строку кода: val dob = thisyear - age. age - это открытый член (то есть переопределенный в подклассе, Student). Таким образом, значение должно быть получено из Student. Проблема в том, что на данный момент Student еще не полностью сконструирован (вспомните первое, что сделал его конструктор - это вызов конструктора суперкласса). Поэтому Person не может спросить Student, какое значение age должно быть, так как это значение еще не установлено. Если вы шагаете по коду и проверяете значение age в этой строке кода, оно равно нулю (значение по умолчанию для Int).

Итак, вопрос в том, что делать? И я думаю, что вы должны спросить: почему Student переопределяет age (и действительно firstname и surname). Есть ли различия в поведении age, firstname и surname между Person и Student? Ответ, вероятно, нет. Поэтому Student не должен переопределять эти свойства: вместо этого он должен объявлять их просто как параметры конструктора (без val или var) и передавать эти значения в базовый класс. Другими словами, Student должно выглядеть следующим образом:

class Student(firstname: String, surname: String, age: Int, val studentID: Int) :
        Person(firstname, surname, age) {
    ...

Вы также можете знать, что ваша строка кода, которая объявляет thisyear, на самом деле создает свойство Person, которое называется thisyear, что, я полагаю, вам не нужно. Любые val или var члены, которые объявлены непосредственно в классе (а не в функции), являются объявлением свойства (и именно поэтому все это вычисляется непосредственно во время создания объекта Person) , Так что, возможно, вы захотите добавить это как:

val dob = Calendar.getInstance().get(Calendar.YEAR) - age

Если вычисление является более сложным и требует больше строк кода, просто создайте закрытый метод (например, calculateDob) и вызовите его, например, val dob = calculateDob(age)

Есть также небольшая аномалия, что age - это var (то есть может измениться), тогда как dob - это val (то есть не может измениться). Таким образом, пользователь может изменить значение age, но dob не будет обновлено. Один из возможных способов решить эту проблему - изменить dob, чтобы вместо свойства, которому в процессе конструирования было присвоено значение (только для чтения), сделать его свойством с помощью метода получения, который будет вычислять значение при каждом вызове, например,

val dob
    get() = Calendar.getInstance().get(Calendar.YEAR) - age

Другой вариант - добавить вспомогательное поле и метод получения / установки для age и обновлять dob всякий раз, когда age обновляется.

0 голосов
/ 07 ноября 2018

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

это именно то, что происходит в вашем примере. В базовом классе Person вы используете возраст открытого члена в конструкторе (строка, в которой вы вычисляете dob). Этого следует избегать, поскольку при выполнении этой строки, вычисляющей dob, age еще не получил значение из производного класса.

Хорошо, я не ответил на вопрос "Как это исправить?" но я надеюсь, что это поможет прояснить, что происходит

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...