Подтвердить что ты не робот

Существует ли разница между расширением протокола Swift 2.0 и абстрактными классами Java/С#?

С добавлением расширений протокола в Swift 2.0 кажется, что протоколы в основном становятся абстрактными классами Java/С#. Единственное отличие, которое я вижу, это то, что абстрактные классы ограничивают одно наследование, тогда как тип Swift может соответствовать любому количеству протоколов.

Является ли это правильным пониманием протоколов в Swift 2.0 или существуют другие отличия?

4b9b3361

Ответ 1

Есть несколько важных отличий...

Расширения протокола могут работать с типами значений, а также классами.

Типы значений - это структуры и перечисления. Например, вы можете расширить IntegerArithmeticType, чтобы добавить свойство isPrime ко всем целым типам (UInt8, Int32 и т.д.). Или вы можете объединить расширения протокола с расширениями структуры, чтобы добавить те же функции к нескольким существующим типам - скажем, добавив векторную арифметическую поддержку как к CGPoint, так и к CGVector.

Java и С# на самом деле не имеют пользовательских/расширяемых типов "простых старых данных" на уровне языка, поэтому здесь не существует аналога. Swift использует типы значений много - в отличие от ObjC, С# и Java, в Swift даже коллекции являются типами копирования-на-записи. Это помогает решить множество проблем, связанных с изменчивостью и безопасностью потоков, поэтому создание собственных типов значений вместо использования классов может помочь вам лучше писать код. (См. Создание лучших приложений со значениями типов в Swift из WWDC15.)

Расширения протокола могут быть ограничены.

Например, у вас может быть расширение, которое добавляет методы CollectionType только в том случае, если тип базового элемента коллекции соответствует некоторым критериям. Здесь тот, который находит максимальный элемент коллекции - в наборе чисел или строк, это свойство появляется, но в коллекции, скажем, UIView (которые не Comparable), этого свойства не существует.

extension CollectionType where Self.Generator.Element: Comparable {
    var max: Self.Generator.Element {
        var best = self[self.startIndex]
        for elt in self {
            if elt > best {
                best = elt
            }
        }
        return best
    }
}

(подсказка для шляпы: этот пример появился на отличном NSBlog только сегодня.)

Есть еще несколько хороших примеров ограниченных расширений протокола в этих переговорах WWDC15 (и, вероятно, больше, но пока я еще не догонял видео):

Абстрактные классы - на любом языке, включая ObjC или Swift, где они являются соглашением по кодированию, а не языковой функцией - работают по линиям наследования классов, поэтому все подклассы наследуют функциональность абстрактного класса, имеет ли смысл смысл или нет.

Протоколы могут выбирать статическую или динамическую отправку.

Этот еще один головорезчик, но может быть действительно мощным, если его использовать хорошо. Вот базовый пример (опять же из NSBlog):

protocol P {
    func a()
}
extension P {
    func a() { print("default implementation of A") }
    func b() { print("default implementation of B") }
}
struct S: P {
    func a() { print("specialized implementation of A") }
    func b() { print("specialized implementation of B") }
}

let p: P = S()
p.a() // -> "specialized implementation of A"
p.b() // -> "default implementation of B"

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

Тип может получить функциональность расширения из нескольких протоколов.

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

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

  • Конфликты между расширениями протокола всегда разрешаются в пользу самого ограниченного расширения. Так, например, метод коллекций представлений всегда выигрывает по одноименному методу для произвольных коллекций (который, в свою очередь, выигрывает над однотипными методами в произвольных последовательностях, потому что CollectionType является подтипом SequenceType).

  • Вызов API, который в противном случае конфликтует, является ошибкой компиляции, а не неопределенностью среды выполнения.

Протоколы (и расширения) не могут создавать хранилище.

Определение протокола может требовать, чтобы типы, принимающие протокол, должны обладать свойством:

protocol Named {
    var name: String { get } // or { get set } for readwrite 
}

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

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

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