¿Cómo evoluciona y versiona una interfaz?

22

Digamos que tienes una interfaz IFoo:

public interface IFoo {
    void Bar(string s);
    int Quux(object o);
}

En la versión 2 de su API, debe agregar un método Glarg a esta interfaz. ¿Cómo lo hace sin romper sus usuarios API existentes y mantener la compatibilidad con versiones anteriores? Esto está dirigido principalmente a .NET, pero también puede aplicarse a otros marcos e idiomas.

thecoop
fuente
Puedes agregar sin problemas. El problema surge cuando cambia / elimina algo que ya estaba allí.
Rig
1
@Rig: al menos en C #, obtendrá un error de compilación si agrega un método a una interfaz y no lo agrega a las clases que implementan esa interfaz.
Malice
Bien, eso es cierto. Estaba pensando más en el escenario de la clase de usuario, que puede ser de un caos mínimo en comparación con cambiar una firma de método o eliminar una. Así que supongo que podría llevar a algún trabajo si necesita agregar a su interfaz.
Aparejo

Respuestas:

9

En la versión 2 de su API, debe agregar un método Glarg a esta interfaz.

¿Por qué?

Las interfaces definidas para su uso con una API tienen dos roles completamente diferentes:

  1. Inversión de dependencia: su API consume tales interfaces. Permiten que el código del cliente cree complementos, etc.
  2. Abstracción: su API devuelve dichas interfaces y oculta los detalles de implementación de los objetos devueltos.

Ahora, para una versión dada de una API, la misma interfaz puede actuar como ambas. Aún así, en versiones futuras, esto se puede desacoplar.

  1. Desea extraer más información de la interfaz que consume. Para mejorar el rendimiento, o agregar flexibilidad o lo que sea. Defina una nueva interfaz, posiblemente derivada de la anterior, y cree un método separado que la consuma. AFAIK la mayoría de los lenguajes .NET permiten la sobrecarga de métodos, por lo que esto puede suceder sin agregar mucho desorden.
  2. Desea "devolver más", es decir, la abstracción de un objeto "más rico" de su API. Aquí tienes dos opciones:

    • Puede suponer razonablemente que el código del cliente no tendrá sus propios implementadores de la interfaz. Bajo este supuesto, es seguro agregar sus extensiones a la interfaz existente.
    • Defina una nueva interfaz, si es posible, derivada de la anterior. Si tal derivación es imposible, cree métodos separados para consultar instancias de la nueva interfaz o use la composición:

      interface MyNewInterface extends MyOldInterface { 
           FancyNewInterface getFancyShit();
      }
      
back2dos
fuente
15

DirectX agregó números de versión a sus interfaces. En su caso, la solución sería algo así como

public interface IFoo2 : IFoo
{
    void Glarg();
}

La API todavía se referiría a IFoo y a IFoo2 solo en métodos, etc., donde se requiere la funcionalidad de IFoo2.

La implementación de la API debe verificar en los métodos existentes (= versión 1) si un objeto de parámetro IFoo realmente implementa IFoo2, si la semántica del método es diferente para IFoo2.

devio
fuente
3

Agregar un nuevo método (o métodos) a su API debe hacerse de tal manera que no tenga ningún efecto secundario en la API existente. Lo que es más importante, alguien que continúe usando la antigua API como si la nueva API no existiera, no debería verse afectada por ella. Usar la API anterior no debería tener efectos secundarios inesperados en la nueva API.

Si alguno de los métodos existentes en la API es reemplazado por los nuevos, no los elimine de inmediato. Márquelos como obsoletos y proporcione una explicación de lo que debe usarse en su lugar. Eso les advierte a los usuarios de su código que futuras versiones ya no lo admitirán en lugar de romper su código sin previo aviso.

Si las API nuevas y antiguas son incompatibles y no pueden vivir juntas sin efectos secundarios no deseados, sepárelas y documente que si se va a adoptar la nueva API, la antigua API debe retirarse por completo. Esto es menos deseable ya que siempre habrá alguien que intente usar ambos y se sienta frustrado cuando no funciona.

Dado que usted preguntó acerca de .NET específicamente, es posible que desee leer este artículo sobre desuso en .NET, que se vincula a ObsoleteAttribute(utilizado en el siguiente ejemplo):

using System;

public sealed class App {
   static void Main() {      
      // The line below causes the compiler to issue a warning:
      // 'App.SomeDeprecatedMethod()' is obsolete: 'Do not call this method.'
      SomeDeprecatedMethod();
   }

   // The method below is marked with the ObsoleteAttribute. 
   // Any code that attempts to call this method will get a warning.
   [Obsolete("Do not call this method.")]
   private static void SomeDeprecatedMethod() { }
}
Gyan alias Gary Buyn
fuente
2

Los cambios en la interfaz pública implican roturas. La estrategia común es hacer esto solo en las versiones principales y después de un período de congelación (para que no ocurra por capricho). Puede escapar sin interrumpir a sus clientes si agrega adiciones a una nueva interfaz (y su implementación puede proporcionar ambos en la misma clase). Eso no es lo ideal, y si sigues haciéndolo tendrás un desastre.

Sin embargo, con otros tipos de modificación (eliminación de métodos, cambio de firmas), está atascado.

Tamás Szelei
fuente
2
Puede reservar de manera preventiva un prefijo para futuros nombres de métodos y advertir a todos los usuarios que no deben usar ese espacio de nombres, pero incluso eso lo convierte en una API poco elegante. En general, los padres tiene toda la razón: la eliminación (y, a menudo, además) de los métodos serán romper los usuarios existentes, y no hay nada que pueda hacer al respecto, excepto que el plan sabiamente.
Kilian Foth
1

Una interfaz es un contrato, por lo tanto, no debe tener versiones. ¿Qué sucede si un jugador de fútbol obtiene un nuevo contrato? ¿Sigue siendo válido el anterior? No. Si uno cambia la interfaz, el contrato cambia y el contrato anterior (interfaz) ya no es válido.

Aunque podría utilizar la estrategia IFoo2, eventualmente eso se volverá complicado cuando tenga:

  • IFoo2
  • IFoo3
  • IFoo4
  • etc.

Yuck

Una API es diferente. Doy biblioteca de código para usar. El mes que viene te doy una biblioteca actualizada. Como ha dicho otro póster, no rompa lo que ya estoy usando, solo agregue nuevas funciones / métodos.

Si quieres versionar algo, usa una clase abstracta en lugar de una interfaz.

Jon Raynor
fuente