Протоколы в Swift: чем они могут быть полезны и как их использовать?

Работа с протоколами — одна из основных функций программирования на Swift. С помощью протоколов вы определяете правила, которым должен соответствовать принимающий класс, структура или перечисление. Этот принцип позволяет вам писать модульный и легко расширяемый код.

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

Что из себя представляют протоколы?

Давайте посмотрим на простой протокол:

protocol Edible {
    func eat()
}

Протокол называется Edible, и мы будем использовать его для объектов, которые можно есть, например, для еды.

Аналогично тому, как работают классы , вы определяете протокол с помощью следующего синтаксиса:

protocol [название протокола] {
    [тело протокола]
}

Протокол Edible определяет только одну функцию eat(). При этом мы только определяем эту функцию, но не создаем ее реализацию. Протоколы просто определяют методы и свойства, но не реализуют их.

Можно сказать, что протокол определяет правила или «требования». Другие классы могут затем принять эти правила и обеспечить их фактическую реализацию. Любой класс, который удовлетворяет правилам протокола, соответствует этому протоколу.

Давайте посмотрим, как класс может принять протокол Edible:

class Apple: Edible {
    func eat() {
        print("Я ем яблоки.")
    }
}

Класс Apple принимает протокол Edible, который указывается после имени класса, разделенного двоеточием. Класс Apple соответствует к протоколу Edible путем реализации функции eat(). Он повторяет объявление функции и предоставляет тело функции.

Мы определили протокол Edible и реализовали его в классе Apple. Можно сказать, что яблоко можно есть, потому что оно выполняет функцию eat(). То есть мы формализовали эту связь со структурой нашего кода.

Почему протоколы полезны?

Зачем вообще использовать протоколы? Разве мы не могли бы просто реализовать функцию eat() в классе Apple?

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

Создадим обычный класс:

class Person {
    var name: String = ""
 
    func provideSnack(withItem item: Apple) {
        item.eat()
    }
}

В приведенном выше примере кода вы создали класс Person. У него есть одно свойство name типа String. Также у него есть одна функция provideSnack(withItem:). Функция provideSnack(withItem:) имеет параметр item типа Apple.

let apple = Apple()
 
let bob = Person()
bob.name = "Боб"
 
bob.provideSnack(withItem: apple)
 
// Я ем яблоки.

В чем проблема с классом Person? Он позволяет есть только яблоки. И что еще хуже: классы Person и Apple теперь тесно связаны, потому что они полагаются на реализацию друг друга, чтобы функционировать. Есть ли способ преодолеть этот недостаток? Вот где нам понадобятся протоколы.

Во-первых, мы изменим функцию provideSnack(withItem:):

func provideSnack(withItem item: Edible) {
    item.eat()
}

Тип item теперь Edible вместо Apple. Итак, мы указали, что item может быть любого типа, если он соответствует Edible.

Создадим другой класс еды:

class CandyBar: Edible {
    func eat() {
        print("Какая вкусная конфета!")
    }
}

Класс CandyBar также принимает и соответствует протоколу Edible. Вы можете легко использовать экземпляр этого класса для функции provideSnack(withItem:):

let candy = CandyBar()
 
bob.provideSnack(withItem: candy)
 
// Какая вкусная конфета!

По сути, функция provideSnack(withItem:) не заботится о том, какой именно item вы предоставляете. Главное, чтобы он соответствовал протоколу Edible.

Поскольку протокол определяет функцию eat(), вы можете вызвать функцию item без необходимости знать ее точную реализацию. То есть класс Person может использовать любой класс, который соответствует Edible, не зная точной реализации этого класса. Именно в этом сила протоколов!

Протоколы как типы данных

Протоколы являются полноценными типами в Swift. Это означает, что вы можете использовать протоколы везде, где можно использовать другие типы.

func provideSnacks(items: [Edible]) {
    for item in items {
        item.eat()
    }
}

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

Поскольку протоколы являются типами, вы можете использовать их во многих местах, включая:

  • В качестве типа параметра или типа возвращаемого значения в функции.
  • В качестве типа константы или переменной.
  • В качестве типа в массиве, словаре или другой коллекции.

Вы даже можете комбинировать протоколы и дженерики, используя конструкцию associatedtype.

Как вы можете использовать протоколы в практической разработке? Давайте посмотрим на несколько примеров:

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

Внедрение зависимостей — это метод, облегчающий тестирование компонентов вашего кода.

Протокольно-ориентированное программирование (POP) — это сборник принципов, концепций и практик, которые придают протоколам более заметную роль в практической разработке на iOS. Вы можете увидеть протокольно-ориентированное программирование как расширение объектно-ориентированного программирования.

С помощью POP вы создаете функциональность в своем приложении, составляя различные протоколы, вместо того, чтобы определять жесткую и иерархическую структуру классов с помощью наследования. Протокольно-ориентированное программирование повышает модульность вашего кода, делая отдельные компоненты более компактными, и в то же время предоставляет больший контроль над реализацией функциональности.

Читайте также:
Добавить комментарий

Ваш адрес email не будет опубликован.