Swift: протокол и базовый класс с одинаковым именем функции - PullRequest
0 голосов
/ 25 апреля 2020

Так что я просто играл с протоколами и просто запутался в следующем поведении. У меня есть протокол с функцией и базовый класс с тем же именем функции. Теперь другой класс наследует от базового класса и реализует протокол. Но это не приводит к ошибке в некоторых сценариях ios.

Например:

protocol P {
    func greetings()
}

class Base {
    func greetings() {
        print("Hello Base")
    }
}

class A: Base {

    override func greetings() {
        print("Hello Class")
    }
}

extension A: P {

}

let a = A()
print(a.greetings()) // print: Hello Class

let b:Base = A()
print(b.greetings()) // print: Hello Class

let p:P = A()
print(p.greetings()) // print: Hello Class

Итак, как происходит соответствие класса протоколу? ?

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

protocol P {
    func greetings()
}
extension P {
    func greetings() {
       print("Hello Protocol")
    }
}
class Base {
    func greetings() {
        print("Hello Base")
    }
}

class A: Base {

}
extension A: P {

}

let a = A()
print(a.greetings()) // print: Hello Base

let b:Base = A()
print(b.greetings()) // print: Hello Base

let p:P = A()
print(p.greetings()) // print: Hello Base

Почему он всегда выбирает функцию базового класса? И почему нет ошибки времени компиляции из-за неоднозначного вызова функции?

1 Ответ

1 голос
/ 26 апреля 2020

Тип переменной, которой вы назначаете объект, не контролирует, как функции отправляются на этот объект. При поиске функции компьютер начинает с объекта и «поднимается» вверх по иерархии классов в поисках реализации. ie. Она начинается с A и, если она не находит функцию, она поднимается до Base (суперкласс A) и ищет там. Если все еще не найден, он переходит к следующему суперклассу (которого в данном случае нет). Если функция не найдена, вы получите сообщение об ошибке.

В первом примере у вас есть экземпляр A, а A переопределяет базовую greetings, так что реализация всегда будет используемый. Это связано с важной концепцией в объектно-ориентированном программировании - Заменяемость .

В этом случае вы можете объявить переменную типа Base, но назначить экземпляр более специализированного класса - A, но программа по-прежнему работает правильно.

Помните, что вывод типа, как показано в вашем первом назначении (let a = A()), присутствует не на всех языках. Во многих случаях вам необходимо явно объявить тип переменной. Если объявление переменной типа Base означает, что вместо подкласса была вызвана функция Base, это исключило бы назначение подклассов.

Что касается вашего вопроса

как происходит соответствие класса протоколу?

Протокол - это спецификация, но не реализация (далее мы рассмотрим ваш вопрос относительно реализаций по умолчанию). Ваш протокол говорит, что все вещи, которые соответствуют P, должны обеспечивать функцию greeting. Очевидно, что и Base, и A обеспечивают функцию приветствия. Ваше расширение просто добавляет условие, что A соответствует P. Не нужно добавлять реализацию P, потому что метод greeting уже существует.

Если вы удалите extension A: P, тогда let p:P = A() выдаст ошибку, потому что компилятор больше не знает, что A соответствует P.

Это подводит нас к последним двум вопросам:

Почему он всегда выбирает функцию базового класса? И Почему нет ошибки времени компиляции из-за неоднозначного вызова функции?

С Язык программирования Swift

Вы можете использовать расширения протокола, чтобы обеспечить реализацию по умолчанию к любому методу или требованию вычисляемого свойства этого протокола. Если соответствующий тип обеспечивает собственную реализацию требуемого метода или свойства, эта реализация будет использоваться вместо той, которая предусмотрена расширением.

Во втором примере вашего блока A не переопределите Base реализацию greeting, поэтому реализация Base всегда вызывается.

Реализация по умолчанию greeting, которую вы добавили к P, будет использоваться только в том случае, если класс, соответствующий P, не обеспечивает реализацию.

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

Рассмотрим следующий пример:

Protocol Vehicle {
    func soundHorn()
    var numberOfDoors: Int
}

extension Vehicle {
    func soundHorn() {
        print("Beep")
    }
}

class SportsCar: Vehicle {
    var numberOfDoors: Int {
        return 2
    }
}

class Sedan: Vehicle {
    var numberOfDoors: Int {
        return 4
    }
}

class Truck: Vehicle {
    var numberOfDoors: Int {
        return 2
    } 

    func soundHorn() { 
        print("**HONK**")
    }
}

Здесь мы определили простой протокол Vehicle, который говорит, что Vehicle имеет несколько дверей и может звучать как рог. Мы предоставили soundHorn по умолчанию, который печатает "Beep". Затем мы объявили классы, которые реализуют этот протокол. Два разных типа автомобилей имеют разное количество дверей, но мы довольны звуковым сигналом по умолчанию. Для Truck у нас есть больший, более громкий гудок, поэтому мы реализуем soundHorn вместо использования по умолчанию:

var v: Vehicle = SportsCar()
v.soundHorn() // Prints "beep"
v = Truck()
v.soundHorn() // Prints "**HONK**"

Обратите внимание, что v имеет тип Vehicle, но мы получаем правильный рог на основе объекта, который фактически назначен на v. Компилятор счастлив, потому что он знает, что все Vehicles могут soundHorn; ему не нужно знать конкретный тип транспортного средства c.

...