Возможная ошибка с протоколами смешивания Swift 3, расширениями и наследованием классов

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

protocol GodsWill           {          func conforms() }
extension GodsWill          {          func conforms() { print("Everything conforms to God's Will") } }
class TheUniverse: GodsWill {          func conforms() { print("The Universe conforms to God's Will") } }
class Life: TheUniverse     { override func conforms() { print("Life conforms to God's Will") } }
class Humans: Life          { override func conforms() { print("Though created by God, Humans think they know better") } }

let universe = TheUniverse()
let life = Life()
let humans = Humans()

universe.conforms()
life.conforms()
humans.conforms()
print("-------------------------")
let array:[GodsWill] = [universe,life,humans]
for item in array { item.conforms() }

И вот результат:

The Universe conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better
-------------------------
The Universe conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better

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

protocol GodsWill           {          func conforms() }
extension GodsWill          {          func conforms() { print("Everything conforms to God's Will") } }
class TheUniverse: GodsWill {  }
class Life: TheUniverse     {          func conforms() { print("Life conforms to God's Will") } }
class Humans: Life          { override func conforms() { print("Though created by God, Humans sometimes think they know better") } }

let universe = TheUniverse()
let life = Life()
let humans = Humans()

universe.conforms()
life.conforms()
humans.conforms()
print("-------------------------")
let array:[GodsWill] = [universe,life,humans]
for item in array { item.conforms() }

Обратите внимание, что TheUniverse не имеет пользовательской реализации конформов(). Вот результат:

Everything conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better
-------------------------
Everything conforms to God's Will
Everything conforms to God's Will
Everything conforms to God's Will

Первые три строки print() — это именно то, что я ожидаю и хочу, но последние три меня действительно сбивают с толку. Так как const() является требованием протокола, они должны быть идентичны трем верхним строкам. Но я получаю поведение, как будто в расширении протокола реализована функция соответствия(), но она не указана в качестве требования протокола. В справочнике по языку программирования Swift об этом ничего нет. И это видео WWDC ровно в 30:40 доказывает мою точка.

Итак, я сделал что-то не так, неправильно понял функциональность или нашел ошибку в Swift 3?


person mogelbuster    schedule 23.10.2016    source источник


Ответы (1)


При дальнейшем расследовании я не думаю, что это связано с видео WWDC.

Юниверс не определяет метод conforms, поэтому при вызове conforms для юниверса будет напечатано "Все...".

Жизнь определяет метод conforms. Однако, поскольку он наследуется от юниверса, который уже имеет реализацию conforms из-за расширения, метод conforms на самом деле не требуется протоколом. Поэтому протокол как бы игнорирует метод conforms в классе Life. Другими словами, метод conforms в Life закрывает метод conforms в расширении протокола, как мы видим в выводе:

let a: TheUniverse = Life()
a.conforms()
Life().conforms()
// output:
// Everything conforms to God's Will
// Life conforms to God's Will

У людей есть еще один conforms метод. На этот раз он определяется с помощью override. Однако этот override не переопределяет метод conforms в расширении протокола. Вместо этого он переопределяет метод conforms в Life, который является прямым суперклассом Humans. Это можно доказать выводом кода ниже:

let a: Life = Humans()
a.conforms()
// output:
// Though created by God, Humans sometimes think they know better

Так ведь метод в расширении протокола не переопределяется. Поэтому, когда вы создаете какие-то объекты и помещаете их в [GodsWill], затенение не имеет никакого эффекта, поэтому вызывается метод расширения протокола.

person Sweeper    schedule 23.10.2016
comment
Итак, каким будет решение этой проблемы? У меня есть несколько классов, которые будут соответствовать GodsWill, и я хочу, чтобы все они имели одинаковую реализацию по умолчанию. Я полагаю, что если бы я мог включить в базовый класс заглушку, которая просто вызывает реализацию расширения, это завершило бы схему и функционировало должным образом. Я пробовал это: func conforms() { super.conforms() } в TheUniverse, но это ошибка времени компиляции. Я пробовал func conforms() { (self as GodsWill).conforms() } в TheUniverse, но это бесконечный цикл. Как бы я вызывал conforms() для GodsWill из TheUniverse? - person mogelbuster; 23.10.2016
comment
@mogelbuster О, да, я забыл упомянуть об этом! Просто не делайте ничего наследуемым от TheUniverse. Почему Life не может напрямую соответствовать GodsWill? - person Sweeper; 23.10.2016
comment
Life должен наследовать от TheUniverse, а Life должен соответствовать GodsWill. Это сработает, за исключением того, что TheUniverse также должно соответствовать GodsWill. Таким образом, Life не может напрямую соответствовать GodsWill. Я мог бы просто поместить реализацию conforms() по умолчанию внутрь TheUniverse, но допустим, реальность больше, чем мы думали, и у меня есть еще два базовых класса EvilParallelUniverse и HeavenlyParallelUniverse, оба из которых должны соответствовать GodsWill. Все они используют реализацию conforms() по умолчанию, но их множество подклассов должны настраивать эту реализацию. - person mogelbuster; 23.10.2016
comment
Я полагаю, что мог бы сделать GodsWill базовым классом для классов юниверса вместо протокола, но это сократит повторное использование кода и расширяемость GodsWill, что и является целью расширений быстрого протокола. В качестве альтернативы я мог бы создать новый базовый класс TheMultiverse, который является суперклассом для всех вселенных, удалить расширение протокола GodsWill (но сохранить сам протокол) и переместить реализацию по умолчанию conforms() внутрь TheMultiverse. Но если мне все это придется делать, то для чего нужны расширения протокола? Это похоже на быструю ошибку дизайна. - person mogelbuster; 23.10.2016