ProgrammazioneSviluppatore Swift middle

Cos'è la composizione dei protocolli in Swift, come funziona e a cosa serve? Quali sono i problemi quando si utilizzano più protocolli contemporaneamente?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • Permette di esprimere in modo flessibile la composizione del comportamento senza gerarchie di ereditarietà
  • Garantisce che tutti i contratti vengano rispettati contemporaneamente
  • Compatibile con parametri generici e alias di tipo

Domande trabocchetto.

È 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.

Errori comuni e antipattern

  • Usare la composizione con otto-nove protocolli: questo è un segno di sovraccarico dell'architettura e di una cattiva separazione delle responsabilità
  • Cast di un tipo valore (struct) in una variabile di composizione di protocollo con limitazione AnyObject — darà sempre errore
  • Utilizzare la stessa composizione in diverse parti dell'applicazione senza typealias: complica la leggibilità

Esempio nella vita reale

Caso negativo

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:

  • Non è necessaria un'ereditarietà profonda
  • Facile aggiungere o rimuovere funzionalità

Svantaggi:

  • Difficile rintracciare il disallineamento
  • Test complessi
  • Scarsa leggibilità delle dichiarazioni

Caso positivo

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:

  • Il codice è più facile da leggere e mantenere
  • I test evidenziano chiaramente il comportamento per DynamicEntity
  • Modifica rapida dell'elenco dei requisiti

Svantaggi:

  • Richiede una rivalutazione dell'architettura
  • A volte è necessario suddividere le classi esistenti per soddisfare i requisiti