Наблюдая видеоурок от Apple, кажется, что быстрое программирование langue и apple поощряют программистов использовать протокол, чем класс. Но, по моему личному мнению, я не вижу явных преимуществ для протокола. класс может соответствовать протоколу, но они также могут наследовать от суперкласса. Мы можем добавить расширение к протоколу, но мы также можем добавить расширение к классу. Мы можем реализовать функции в классах, которые соответствуют протоколу, но мы также можем переопределить func в подклассе. Я все еще смущен тем, что нам нужно использовать протокол, а не класс. И когда мы должны использовать протокол вместо класса?
Почему протокол лучше, чем класс в быстром?
Ответ 1
Давайте рассмотрим пример загрузки.
У вас есть базовый класс FileDownloadModel и 3 подкласса AudioFileDownloadModel, VideoFileDownloadModel и ImageDownloadModel.
У вас есть DownloadManager, который принимает входные данные FileDownloadModel и использует эту модель urlToDownload для загрузки файла.
Позже вам сообщат, что есть еще одна модель, но она имеет тип UserDownloadModel, который является подклассом пользователя, а не FileDownloadModel.
Видите, теперь становится трудно справиться с этим сценарием, когда вам придется изменить много кода, чтобы включить методы загрузки.
Как протоколно-ориентированное программирование поможет вам здесь:
- Создайте протокол с именем DownloadingFileProtocol и добавьте методы и свойства, необходимые для загрузки файла. например. urlToDownload, pathToSave, расширение и т.д.
- Реализуйте один и тот же протокол в FileDownloadModel и UserDownloadModel. Преимущество здесь в том, что вам не нужно менять много кода в UserDownloadModel. Вы просто реализуете методы из DownloadingFileProtocol.
- Если новая сущность снова появится на линии, вы не будете изменять какой-либо код. Скорее, вы просто реализуете методы протокола.
- И теперь ваш DownloadManager может принимать DownloadingFileProtocol в качестве входных данных вместо конкретной модели. Кроме того, теперь вы можете сделать любую модель "загружаемой", приняв этот протокол.
Ответ 2
класс и протокол - ортогональные понятия. Протокол пересекает дерево классов и объединяет один или несколько классов с разрозненной родословной.
Возможно, проще:
- "class" определяет, что такое объект.
- "protocol" определяет поведение объекта.
Итак, у вас есть класс Car:
class Car {
var bodyStyle : String
}
и класс Цвет:
class Color {
var red : Int
var green : Int
var blue : Int
}
Теперь, более или менее очевидно, что Colors and Cars совершенно не связаны, однако предположим, что я хочу иметь возможность легко конвертировать один из строк в Strings, поэтому я могу отлаживать с помощью:
print(Car(...))
или
print(Color(...))
Для этой цели язык Swift определяет протокол CustomStringConvertible
, поэтому мы можем объявить, что a Car можно распечатать, используя этот протокол:
extension Car : CustomStringConvertible {
var description : String { get { return "Car: \(bodyStyle)" } }
}
и цвет:
extension Color : CustomStringConvertible {
var description : String { get { return "Color: \(red) \(green) \(blue)" } }
}
Итак, прежде чем мне понадобился один метод печати для каждого класса, теперь мне нужен только один метод печати, который выглядит примерно так:
func print(data:CustomStringConvertible) {
let string = data.description
... bunch of code to actually print the line
}
Это возможно, потому что объявление того, что класс реализует протокол, является обещанием использовать методы из протокола, зная, что они реализованы и (предположительно) делают то, что ожидалось.
Ответ 3
Во многом это иерархия типов. Допустим, у вас есть объект, представляющий GlowingRedCube
, но вы хотите, чтобы этот тип использовался во множестве общих кодов, которые волнуют:
- Различные фигуры -
Cube
extendsShape
- Различные цвета -
Red
extendsColorful
- Различные типы освещения -
Glowing
extendsIlluminated
У тебя проблемы. Вы можете выбрать базовый класс и добавить специализации: GlowingRedCube
extends GlowingCube
extends Shape
, но затем вы получите очень широкий набор классов и негибкий набор вещей (что, если вы хотите сделать SoftRedCube
, но сохраните какие-либо методы, которые вы определили для своего существующего типа красного куба?)
Вы можете просто иметь Cube
и иметь свойства освещения и формы, но тогда вы не получите хорошую проверку типа компилятора: если у вас есть метод Room.lightUp()
и вам нужно передать его Cube
, вы тогда необходимо проверить, включает ли этот тип любое освещение! Если вы можете передать его только Illuminated
, тогда компилятор остановит вас, как только вы попробуете.
Протоколы позволяют отделить это: GlowingRedCube
может реализовать протокол Illuminated
, протокол Colorful
и протокол Shape
. Из-за расширений протокола вы можете включить функциональные возможности по умолчанию, поэтому вам не нужно выбирать уровень иерархии для его присоединения.
struct GlowingRedCube: Shape, Colorful, Illuminated {
// ..
}
Эффективно, протоколы позволяют прикрепить поведение к объекту, независимо от того, что еще делает этот объект. Именно поэтому они используются для таких вещей, как протоколы делегатов и источников данных: даже если вы в основном привязываете эти вещи к ViewController
, базовый объект не имеет значения, поэтому вы можете быть гибкими в отношении того, как вы реализуете.
В Swift гораздо больше используется протокол Swift, чем просто основы: они исключительно мощные, поскольку они могут быть привязаны к нескольким различным конструкциям кода: классам, структурам и перечислениям. Это позволяет вам в самом деле приблизиться к протоколу программирования. Там был отличный видеоролик об этом подходе от WWDC в прошлом году, но он помогает потратить некоторое время на то, чтобы попробовать некоторые разные структуры объектов, чтобы сначала почувствовать для проблем.
Ответ 4
С протоколами один класс/структура может использоваться как разные вещи. Например, структура String
соответствует soooo многим протоколам!
Comparable
CustomDebugStringConvertible
Equatable
ExtendedGraphemeClusterLiteralConvertible
Hashable
MirrorPathType
OutputStreamType
Streamable
StringInterpolationConvertible
StringLiteralConvertible
UnicodeScalarLiteralConvertible
Это означает, что String
можно использовать как 11 разных вещей! Если для одного из вышеуказанных протоколов в качестве параметра нужен какой-либо метод, вы можете передать строку.
"Но я могу просто создать класс богов, который имеет все методы, которые имеют протоколы!" вы утверждали. Помните, что вы можете наследовать только один класс из Swift, а множественное наследование настолько опасно, что может сделать ваш код супер сложным.
Кроме того, с помощью протоколов вы можете определить свое пользовательское поведение класса. String
hashcode
метод отличается от метода Int
. Но они оба совместимы с этой функцией:
func doStuff(x: Hashable) {
}
Это истинное чудо протоколов.
И последнее, но не менее важное: протоколы имеют смысл. Протоколы представляют собой "своего рода" или "могут быть использованы как" отношения. Когда X можно использовать как Y, имеет смысл, что X соответствует протоколу Y, правильно?