swift override equality

Swift: Anulando== en la subclase, la invocación de los resultados de== solo en superclase



override equality (1)

Tengo una clase A , que cumple con el protocolo Equatable e implementa la función == . En la subclase B anulo == con más controles.

Sin embargo, cuando hago una comparación entre dos matrices de instancias de B (que ambas tienen el tipo Array<A> ), se invoca == para A Por supuesto, si cambio el tipo de ambas matrices a Array<B> , se invoca == para B

Se me ocurrió la siguiente solución:

A.swift: internal func ==(lhs: A, rhs: A) -> Bool { if lhs is B && rhs is B { return lhs as! B == rhs as! B } return ... }

Que se ve realmente feo y debe extenderse para cada subclase de A ¿Hay una manera de asegurarse de que == para la subclase se invoca primero?


La razón por la que se invoca la igualdad para A para un Array<A> que contiene B es que la sobrecarga de funciones libres se resuelve de forma estática, no dinámica, es decir, en tiempo de compilación según el tipo, no en tiempo de ejecución en función del punto señalado. valorar.

Esto no es sorprendente dado que == no se declara dentro de la clase y luego se invalida en la subclase. Esto puede parecer muy limitante pero, honestamente, definir la igualdad polimórfica utilizando técnicas tradicionales de OO es extremadamente (y engañosamente) difícil. Vea este enlace y este documento para más información.

La solución ingenua podría ser definir una función distribuida dinámicamente en A , luego definir == para llamar a eso:

class A: Equatable { func equalTo(rhs: A) -> Bool { // whatever equality means for two As } } func ==(lhs: A, rhs: A) -> Bool { return lhs.equalTo(rhs) }

Luego, cuando implementas B , deberías anular equalTo :

class B: A { override func equalTo(rhs: A) -> Bool { return (rhs as? B).map { b in return // whatever it means for two Bs to be equal } ?? false // false, assuming a B and an A can’t be Equal } }

¿Todavía tienes que hacer uno as? baile, porque es necesario determinar si el argumento de la mano derecha es una B (si es equalTo Tomó una B directamente, no sería una anulación legítima).

También hay algunos comportamientos posiblemente sorprendentes escondidos aquí:

let x: [A] = [B()] let y: [A] = [A()] // this runs B’s equalTo x == y // this runs A’s equalTo y == x

Es decir, el orden de los argumentos cambia el comportamiento. Esto no es bueno: la gente espera que la igualdad sea simétrica. Entonces realmente necesitas algunas de las técnicas descritas en los enlaces anteriores para resolver esto correctamente.

En ese momento puede sentir que todo esto se está volviendo un poco innecesario. Y probablemente lo sea, especialmente dado el siguiente comentario en la documentación de Equatable en la biblioteca estándar de Swift:

La igualdad implica sustituibilidad . Cuando x == y , x e y son intercambiables en cualquier código que solo depende de sus valores.

La identidad de la instancia de clase, que se distingue por triple-igual === no es parte del valor de una instancia. Se desaconseja la exposición de otros aspectos no valiosos de los tipos Equatable , y los que se exponen deben indicarse explícitamente en la documentación.

Teniendo en cuenta esto, es posible que desee reconsiderar el hecho de Equatable sofisticado con su implementación Equatable , si la forma en que está implementando la igualdad no es de una manera en la que estaría contento con dos valores iguales que se sustituyan entre sí. Una forma de evitar esto es considerar que la identidad del objeto es la medida de la igualdad, e implementar == en términos de === , que solo debe hacerse una vez para la superclase. Alternativamente, podrías preguntarte, ¿ realmente necesitas herencia de implementación? Y si no, considere deshacerse de él y usar tipos de valor en su lugar, y luego usar protocolos y genéricos para capturar el comportamiento polimórfico que está buscando.