In Swift, l'esperienza di molti linguaggi OOP è stata generalizzata e perfezionata attraverso la possibilità di combinare (comporre), invece di ereditare solo i protocolli. La composizione dei protocolli consente di dichiarare una variabile, un parametro di funzione o un generico con un requisito di conformità a più protocolli. Questo meccanismo è estremamente utile quando è necessario lavorare con oggetti che possiedono il comportamento di più contratti (interfacce), evitando in modo flessibile gli svantaggi dell'ereditarietà multipla. Il problema che la composizione deve risolvere è la necessità di esprimere "l'oggetto deve soddisfare un gruppo di requisiti" e non solo uno.
In Swift viene utilizzata una sintassi particolare: l'unione dei protocolli tramite il simbolo & (ampersand), ad esempio, protocolA & protocolB. Dietro le quinte viene eseguito un controllo a runtime (ad esempio, durante il casting dei tipi e la conversione in contesti generici). Questo minimizza il numero di tipi e implementa in modo flessibile il pattern "separazione delle responsabilità".
Esempio di codice:
protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite disegna") } func move() { print("Sprite si muove") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)
Caratteristiche principali:
È possibile creare una variabile di tipo solo protocolA & protocolB, senza legarsi a una particolare struttura o classe?
Sì, è possibile dichiarare una variabile come conforme a più protocolli, ad esempio:
var obj: protocolA & protocolB
Ma è importante: tali variabili possono riferirsi solo a oggetti (e non a tipi valore), se nella composizione almeno uno dei protocolli è limitato a tipi di classe (protocol: AnyObject).
Posso includere un tipo di classe nella composizione, ad esempio, SomeClass & Drawable?
Sì, ma con delle sfumature: una composizione del tipo SomeClass & Protocol richiede che i valori siano necessariamente istanze di quella classe (o delle sue sottoclassi) che implementano il protocollo. Questo approccio è usato per limitare i tipi generici.
Posso usare la composizione dei protocolli come tipo di tipi associati nell'estensione del protocollo?
Sì, ma ci sono limitazioni: non è possibile dichiarare associatedtype come composizione, ma è possibile utilizzare where nell'estensione per limitare protocolli di composizione, ad esempio, un'estensione applicabile solo a tipi che soddisfano più protocolli.
Nel progetto sono stati implementati 5 protocolli simili — Drawable, Movable, Resizable, Colorable, Animatable. È stata usata ovunque la composizione Drawable & Movable & Resizable & Colorable & Animatable. Errori tipici sono stati accompagnati da bug complessi a causa del fatto che alcune entità non implementavano uno dei contratti.
Vantaggi:
Svantaggi:
Invece di una composizione complessa, abbiamo evidenziato due protocolli principali (ad esempio, Actor e Viewable), creato typealias per la composizione "DynamicEntity" e lo abbiamo utilizzato ovunque. Abbiamo chiaramente separato le aree di responsabilità.
Vantaggi:
Svantaggi: