ProgramaciónDesarrollador Swift middle

¿Qué es la composición de protocolos en Swift, cómo funciona y para qué se necesita? ¿Cuáles son las trampas al utilizar varios protocolos al mismo tiempo?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

En Swift, la experiencia de muchos lenguajes de programación orientados a objetos se ha generalizado y perfeccionado a través de la posibilidad de combinar, en lugar de solo heredar, protocolos. La composición de protocolos permite declarar una variable, un parámetro de función o un genérico con el requisito de cumplir varios protocolos a la vez. Este mecanismo es extremadamente útil cuando es necesario trabajar con objetos que tienen el comportamiento de varios contratos (interfaces), al mismo tiempo que se evita de forma flexible los inconvenientes de la herencia múltiple. El problema que resuelve la composición es la necesidad de expresar "el objeto debe cumplir un grupo de requisitos", y no solo uno.

En Swift se utiliza una sintaxis especial: la unión de protocolos se indica con el símbolo & (ampersand), por ejemplo, protocolA & protocolB. Bajo el capó, se realiza una verificación en tiempo de ejecución (por ejemplo, al realizar conversiones de tipos y al convertir en contextos genéricos). Esto minimiza la cantidad de tipos y aplica de forma flexible el patrón de "separación de responsabilidades".

Ejemplo de código:

protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite dibuja") } func move() { print("Sprite se mueve") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)

Características clave:

  • Permite expresar la composición del comportamiento sin jerarquías de herencia de manera flexible
  • Garantiza que se cumplan todos los contratos a la vez
  • Compatible con parámetros genéricos y alias de tipo

Preguntas trampa.

¿Se puede crear una variable del tipo solo protocolA & protocolB, sin depender de una estructura o clase específica?

Sí, se puede declarar una variable como que cumple varios protocolos a la vez, por ejemplo:

var obj: protocolA & protocolB

Pero es importante: tales variables solo pueden hacer referencia a objetos (y no a tipos de valor), si en la composición al menos uno de los protocolos está restringido a tipos de clase (protocol: AnyObject).

¿Se puede incluir en la composición un tipo de clase, por ejemplo, SomeClass & Drawable?

Sí, pero con matices: la composición del tipo SomeClass & Protocol requiere que los valores sean necesariamente instancias de esa clase (o sus subclases) que implementen el protocolo. Este enfoque se utiliza para restringir tipos genéricos.

¿Se puede usar la composición de protocolos como tipo de tipos asociados en la extensión de protocolos?

Sí, pero hay limitaciones: no se puede declarar associatedtype como composición, pero se puede utilizar where en extensión para restringir los protocolos de composición, por ejemplo, extensión que se aplica solo a tipos que cumplen varios protocolos.

Errores comunes y anti-patrones

  • Usar la composición con ocho o nueve protocolos: esto es un signo de sobrecarga de arquitectura y mala división de responsabilidades
  • Intentar convertir un tipo de valor (struct) a una variable de composición de protocolo con restricción AnyObject siempre dará un error
  • Usar la misma composición en diferentes partes de la aplicación sin typealias: complica la legibilidad

Ejemplo de la vida real

Caso negativo

En el proyecto se implementaron 5 protocolos similares: Drawable, Movable, Resizable, Colorable, Animatable. Se aplicó en todas partes la composición Drawable & Movable & Resizable & Colorable & Animatable. Los errores típicos estaban acompañados de complejos bugs debido a que algunas entidades no implementaban uno de los contratos.

Pros:

  • No se requiere una herencia profunda
  • Fácil agregar o eliminar funcionalidad

Contras:

  • Difícil rastrear la incoherencia
  • Compleja prueba
  • Mala legibilidad de la declaración

Caso positivo

En lugar de una composición compleja, se destacaron dos protocolos principales (por ejemplo, Actor y Viewable), se hizo un typealias para la composición "DynamicEntity" y se usó en todas partes. Se delimitó claramente las áreas de responsabilidad.

Pros:

  • El código se lee y mantiene más fácilmente
  • Las pruebas destacan claramente el comportamiento para DynamicEntity
  • Rápida modificación de la lista de requisitos

Contras:

  • Requiere repensar la arquitectura
  • A veces es necesario dividir las clases existentes para cumplir con los requisitos