В Swift опыт многих ООП-языков был обобщен и совершенствован через возможность комбинировать (композировать), а не только наследовать протоколы. Композиция протоколов позволяет объявить переменную, параметр функции или generic с требованием соответствия сразу нескольким протоколам. Этот механизм чрезвычайно полезен, когда необходимо работать с объектами, обладающими поведением нескольких контрактов (interface), при этом гибко избегая минусов множественного наследования. Проблема, решаемая композицией, — необходимость выразить "объект должен удовлетворять группе требований", а не только одному.
В решении Swift используется особый синтаксис: объединение протоколов знаком & (ampersand), например, protocolA & protocolB. Под капотом осуществляется runtime check (например, при приведении типов и приведении в generic-контекстах). Это минимизирует количество типов и гибко реализует паттерн "разделение обязанностей".
Пример кода:
protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite draws") } func move() { print("Sprite moves") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)
Ключевые особенности:
Можно ли создать переменную типа только protocolA & protocolB, не привязываясь к определённой структуре или классу?
Да, можно объявить переменную как соответствующую сразу нескольким протоколам, например:
var obj: protocolA & protocolB
Но важно: такие переменные могут ссылаться только на объекты (а не value-типов), если в композиции хотя бы один protocol ограничен классовыми типами (protocol: AnyObject).
Можно ли включить в композицию классовый тип, например, SomeClass & Drawable?
Да, но с нюансами: композиция вида SomeClass & Protocol требует, чтобы значения обязательно были экземплярами этого класса (или его наследников), реализующими протокол. Такой подход применяется для ограничения generic-типов.
Можно ли использовать композицию протоколов в качестве типа ассоциированных типов в protocol extension?
Да, но есть ограничения: нельзя объявить associatedtype как композицию, но можно использовать where при extension для ограничения протоколов-композиций, например, extension, применяющееся только к типам, соответствующим нескольким протоколам.
В проекте реализовали 5 похожих протоколов — Drawable, Movable, Resizable, Colorable, Animatable. Везде применялась композиция Drawable & Movable & Resizable & Colorable & Animatable. Типичные ошибки сопровождались сложными багами из-за того, что часть сущностей не реализовывала один из контрактов.
Плюсы:
Минусы:
Вместо сложной композиции выделили два основных протокола (например, Actor и Viewable), сделали typealias для композиции "DynamicEntity" и везде использовали его. Чётко разграничили зоны ответственности.
Плюсы:
Минусы: