In Swift wurde die Erfahrung vieler OOP-Sprachen zusammengefasst und verbessert durch die Möglichkeit, Protokolle zu kombinieren (zu kompositionieren) anstatt sie nur zu erben. Die Protokollkomposition erlaubt es, eine Variable, einen Funktionsparameter oder ein Generic mit der Anforderung zu deklarieren, mehreren Protokollen zu entsprechen. Dieser Mechanismus ist äußerst nützlich, wenn man mit Objekten arbeiten muss, die das Verhalten mehrerer Verträge (Interfaces) besitzen, und dabei flexibel die Nachteile der Mehrfachvererbung vermeiden möchte. Das Problem, das durch die Komposition gelöst wird, ist die Notwendigkeit, "das Objekt muss eine Gruppe von Anforderungen erfüllen" auszudrücken und nicht nur eine einzige.
In Swift wird eine spezielle Syntax verwendet: die Kombination von Protokollen mit dem Zeichen & (Ampersand), zum Beispiel protocolA & protocolB. Im Hintergrund wird ein Runtime-Check durchgeführt (zum Beispiel beim Typcasting und in Generic-Kontexten). Dies minimiert die Anzahl von Typen und implementiert das Muster "Trennung der Verantwortlichkeiten" flexibel.
Beispielcode:
protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite zeichnet") } func move() { print("Sprite bewegt sich") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)
Schlüsselfunktionen:
Kann man eine Variable vom Typ nur protocolA & protocolB erstellen, ohne sich an eine bestimmte Struktur oder Klasse zu binden?
Ja, man kann eine Variable deklarieren, die mehreren Protokollen entspricht, zum Beispiel:
var obj: protocolA & protocolB
Aber wichtig: Solche Variablen können nur auf Objekte (nicht auf Werttypen) verweisen, wenn in der Komposition mindestens ein Protokoll auf Klassentypen eingeschränkt ist (protocol: AnyObject).
Kann man einen Klassentyp in die Komposition einbeziehen, zum Beispiel SomeClass & Drawable?
Ja, aber mit Nuancen: Eine Komposition in der Form SomeClass & Protocol erfordert, dass die Werte unbedingt Instanzen dieser Klasse (oder deren Nachkommen) sind, die das Protokoll implementieren. Dieser Ansatz wird verwendet, um Generic-Typen einzuschränken.
Kann man die Protokollkomposition als Typ von assoziierten Typen in einer Protokollerweiterung verwenden?
Ja, aber es gibt Einschränkungen: Man kann associatedtype nicht als Komposition deklarieren, kann aber where in der Erweiterung verwenden, um Kompositionsprotokolle einzuschränken, zum Beispiel eine Erweiterung, die nur für Typen gilt, die mehreren Protokollen entsprechen.
Im Projekt wurden 5 ähnliche Protokolle implementiert – Drawable, Movable, Resizable, Colorable, Animatable. Überall wurde die Komposition Drawable & Movable & Resizable & Colorable & Animatable verwendet. Typische Fehler gingen mit komplexen Bugs einher, weil Teile der Entitäten einen der Verträge nicht implementierten.
Vorteile:
Nachteile:
Statt einer komplexen Komposition wurden zwei Hauptprotokolle (zum Beispiel Actor und Viewable) hervorgehoben, Typealias für die Komposition "DynamicEntity" erstellt und überall verwendet. Zonen der Verantwortlichkeit wurden klar abgegrenzt.
Vorteile:
Nachteile: