Kompozycja protokołów to mechanizm w Swift, który pozwala na tworzenie typu, który musi spełniać kilka protokołów jednocześnie. Jest to alternatywny sposób na zastąpienie dziedziczenia wielokrotnego, które w Swift dla klas nie istnieje.
Historia zagadnienia
Objective-C wspierało dziedziczenie wielokrotne tylko dla protokołów, ale nie dla klas. Swift rozwija tę tradycję, kładąc nacisk na protokoły i ich kombinacje do budowania nowych abstrakcji.
Problem
Programista często staje przed zadaniem stworzenia typu, którego zachowanie określa kilka abstrakcji. Dziedziczenie wielokrotne nieuchronnie prowadzi do konfliktów hierarchii, w Swift jest to bezpiecznie rozwiązane dzięki protokołom i kompozycji protokołów.
Rozwiązanie
W Swift można łączyć protokoły za pomocą operatora '&'. Pozwala to na tworzenie zmiennych lub parametrów funkcji, które muszą spełniać jednocześnie kilka protokołów.
Przykład kodu:
protocol Drivable { func drive() } protocol Flyable { func fly() } struct FlyingCar: Drivable, Flyable { func drive() { print("Driving") } func fly() { print("Flying") } } func testVehicle(_ vehicle: Drivable & Flyable) { vehicle.drive() vehicle.fly() } testVehicle(FlyingCar())
Kluczowe cechy:
Czy można przekazać obiekt, realizujący tylko jeden z protokołów, do parametru z kompozycją protokołów?
Nie, obiekt musi realizować wszystkie protokoły wchodzące w skład kompozycji, w przeciwnym razie kompilator zgłosi błąd.
Przykład kodu:
// struct Car: Drivable {} — nie można przekazać do testVehicle, ponieważ fly() nie jest zaimplementowane
Czy kompozycja protokołów działa z typami, a nie tylko z wartościami?
Kompozycja protokołów stosuje się do wartości (zmiennych, parametrów funkcji), ale nie jest używana podczas definiowania typu obiektu (na przykład, nie można zadeklarować nowego typu jako „kompozycję” protokołów — tylko zmienną).
Przykład kodu:
var obj: SomeProtocol & AnotherProtocol // dozwolone // typealias MyType = SomeClass & AnotherProtocol // błąd
Czy można łączyć klasy i protokoły przez '&'?
Tak, ale z jednym ograniczeniem: tylko jeden typ klasowy (klasa lub jej potomny) może być po lewej stronie, pozostałe to tylko protokoły, w przeciwnym razie kompilator zgłosi błąd.
Przykład kodu:
class A {} protocol B {} // func f(obj: A & B) {} // dozwolone // func f(obj: A & AnotherClass & B) {} // błąd! Tylko jeden typ klasowy dozwolony
W projekcie do przesyłania danych między warstwami używane są obiekty z kompozycją protokołów, choć wystarczyłby jeden protokół:
func present(item: Displayable & Serializable) { ... }
Zalety:
Użycie kompozycji protokołów tylko w oczywistych przypadkach — na przykład przetwarzanie obiektów, które jednocześnie wspierają Codable oraz Identifiable dla ogólnej serializacji:
func save<T: Codable & Identifiable>(_ item: T) { ... }
Zalety: