Почему значение протокола по умолчанию, переданное функции, не изменяется, даже если функция изменяется при создании подкласса?

У меня есть протокол, которому я присвоил некоторые значения по умолчанию:

protocol HigherProtocol {
    var level: Int { get }
    
    func doSomething()
}

extension HigherProtocol {
    var level: Int { 10 }
    
    func doSomething() {
        print("Higher level is \(level)")
    }
}

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

protocol LowerProtocol: HigherProtocol {}

extension LowerProtocol {
    var level: Int { 1 }
    
    func doSomething() {
        print("Lower level is \(level)")
    }
}

Затем я создаю класс, соответствующий HigherProtocol, а затем подкласс, соответствующий протоколу более низкого уровня:

class HigherClass: HigherProtocol {}

class LowerClass: HigherClass, LowerProtocol {}

Однако когда я создаю экземпляр этого более низкого класса, он показывает странное поведение:

let lowerClass = LowerClass()

lowerClass.level // is 1

lowerClass.doSomething() // Prints "Lower level is 10" to the console.

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

Мне интересно, что здесь происходит?


person Kramer    schedule 19.01.2021    source источник


Ответы (2)


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

Проблема здесь в том, что HigherClass соответствует HigherProtocol и поэтому теперь имеет реализации для level и doSomething. Нижний класс наследуется от этого и хочет переопределить эти реализации. Но переопределения находятся в расширении протокола, что является неопределенным поведением. См. Расширения языка программирования Swift:

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

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

(Кстати, ситуация аналогична в Objective-C. Реализация метода в двух разных категориях делает неопределенным, какой из них вызывается, и нет предупреждений или ошибок, чтобы сообщить вам, когда это произойдет. Оптимизация Swift может сделать поведение даже тем более удивительно.)

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

person Rob Napier    schedule 19.01.2021

Протоколы - это экзистенциальные типы, поэтому вы запутались. Вам нужно открыть типы протоколов вашего класса Type. В вашем случае вы можете сделать LowerProtocol или HigherProtocol, чтобы теперь было напечатано 10. Сделаем так

let lowerClass: LowerProtocol = LowerClass()

or

let lowerClass: HigherProtocol = LowerClass()

lowerClass.level // now prints 10

lowerClass.doSomething() // Prints "Lower level is 10" to the console.
person zeytin    schedule 19.01.2021