Ответ RobNapier (как обычно) неплохой, но только для альтернативной перспективы, которая может оказаться еще более просветляющей ...
Протокол представляет собой абстрактный набор требований - контрольный список, который должен выполнить конкретный тип, чтобы сказать, что он соответствует протоколу. Традиционно считается, что этот контрольный список поведения: методы или свойства, реализованные конкретным типом. Связанные типы - это способ наименования вещей, которые участвуют в таком контрольном списке, и тем самым расширяя определение, сохраняя при этом его открытый конец как , как соответствующий тип реализует соответствие.
Когда вы видите:
protocol SimpleSetType {
associatedtype Element
func insert(_ element: Element)
func contains(_ element: Element) -> Bool
// ...
}
Что означает, что для типа, требующего соответствия SimpleSetType
, не только этот тип содержит функции insert(_:)
и contains(_:)
, эти две функции должны принимать тот же тип параметра, что и друг друга. Но не имеет значения, каков тип этого параметра.
Вы можете реализовать этот протокол с помощью общего или нестандартного типа:
class BagOfBytes: SimpleSetType {
func insert(_ byte: UInt8) { /*...*/ }
func contains(_ byte: UInt8) -> Bool { /*...*/ }
}
struct SetOfEquatables<T: Equatable>: SimpleSetType {
func insert(_ item: T) { /*...*/ }
func contains(_ item: T) -> Bool { /*...*/ }
}
Обратите внимание, что нигде BagOfBytes
или SetOfEquatables
определяют соединение между SimpleSetType.Element
и типом, используемым в качестве параметра для их двух методов - компилятор автоматически выполняет то, что эти типы связаны с правильными методами, поэтому они отвечают требованиям протокола для связанного типа .
Если связанные типы расширяют ваш словарь для создания абстрактных контрольных списков, параметры типового типа ограничивают реализацию конкретного типа. Когда у вас есть общий класс:
class ViewController<V: View> {
var view: V
}
Он не говорит, что существует множество разных способов сделать ViewController
(пока у вас есть view
), это говорит, что ViewController
- настоящая конкретная вещь , и она имеет view
. Кроме того, мы точно не знаем, какой вид имеет данный экземпляр ViewController
, но мы знаем, что он должен быть View
(либо подклассом класса View
, либо типом, реализующим View
... мы не говорим).
Или, говоря иначе, писать общий тип или функцию - это своего рода ярлык для написания реального кода. Возьмем этот пример:
func allEqual<T: Equatable>(a: T, b: T, c: T) {
return a == b && b == c
}
Это имеет такой же эффект, как если бы вы проходили все типы Equatable
и писали:
func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c }
func allEqual(a: String, b: String, c: String) { return a == b && b == c }
func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }
Как вы можете видеть, re , создавая код здесь, реализуя новое поведение - в отличие от связанных с протоколом типов, где мы описываем только требования к выполнению чего-то другого.
] Ассоциированные типы и параметры типового типа - это очень разные виды инструментов: связанные типы - это язык описания, а generics - язык реализации. У них очень разные цели, хотя их использование иногда выглядит одинаково (особенно когда речь идет о тонких взглядах на такие взгляды между абстрактным планом для коллекций любого типа элемента и фактическим типом коллекции, который все еще может иметь общий элемент). Поскольку они очень разные звери, у них есть другой синтаксис.