Comment évoluez-vous et versionnez-vous une interface?

22

Disons que vous avez une interface IFoo:

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

Dans la version 2 de votre API, vous devez ajouter une méthode Glargà cette interface. Comment faire sans casser vos utilisateurs d'API existants et maintenir la compatibilité descendante? Ceci est principalement destiné à .NET, mais peut également s'appliquer à d'autres frameworks et langages.

thecoop
la source
Vous pouvez ajouter sans problème. Le problème survient lorsque vous modifiez / supprimez quelque chose qui était déjà là.
Rig
1
@Rig: En C # au moins, vous obtiendrez une erreur de compilation si vous ajoutez une méthode à une interface et ne l'ajoutez pas aux classes qui implémentent cette interface.
Malice
Eh bien, c'est vrai. Je pensais davantage au scénario de classe d'utilisateurs qui peut être d'un chaos minimal par rapport à la modification d'une signature de méthode ou à sa suppression. Je suppose donc que cela pourrait entraîner un certain travail si vous aviez besoin d'ajouter à votre interface.
Rig

Réponses:

9

Dans la version 2 de votre API, vous devez ajouter une méthode Glarg à cette interface.

Pourquoi?

Les interfaces définies pour une utilisation avec une API ont deux rôles entièrement différents:

  1. Inversion de dépendance - ces interfaces sont consommées par votre API. Ils permettent au code client de créer des plugins, etc.
  2. Abstraction - ces interfaces sont retournées par votre API et masquent les détails d'implémentation des objets retournés.

Maintenant, pour une version donnée d'une API, la même interface peut agir comme les deux. Pourtant, dans les versions futures, cela peut être découplé.

  1. Vous souhaitez extraire plus d'informations de l'interface que vous consommez. Pour améliorer les performances, ou ajouter de la flexibilité ou autre. Définissez une nouvelle interface, éventuellement dérivée de l'ancienne, et créez une méthode distincte en la consommant. AFAIK la plupart des langages .NET autorisent la surcharge de méthode, donc cela peut se produire sans ajouter beaucoup d'encombrement.
  2. Vous voulez "retourner plus", c'est-à-dire l'abstraction d'un objet "plus riche" de votre API. Ici, vous avez deux choix:

    • Vous pouvez raisonnablement supposer que le code client n'aura pas ses propres implémenteurs de l'interface. Dans cette hypothèse, il est sûr d'ajouter vos extensions à l'interface existante.
    • Définissez une nouvelle interface, si possible dérivée de la précédente. Si une telle dérivation est impossible, créez des méthodes distinctes pour rechercher des instances de la nouvelle interface ou utilisez la composition:

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

DirectX a ajouté des numéros de version à ses interfaces. Dans votre cas, la solution serait quelque chose comme

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

L'API ferait toujours référence à IFoo et à IFoo2 uniquement dans les méthodes, etc., où la fonctionnalité IFoo2 est requise.

L'implémentation de l'API doit vérifier dans les méthodes existantes (= version 1) si un objet de paramètre IFoo implémente réellement IFoo2, si la sémantique des méthodes est différente pour IFoo2.

devio
la source
3

L'ajout d'une nouvelle (ou de plusieurs) méthode (s) à votre API doit se faire de manière à ne pas avoir d'effets secondaires sur l'API existante. Plus important encore, une personne qui continue d'utiliser l'ancienne API comme si la nouvelle API n'existait pas ne devrait pas être affectée par celle-ci. L'utilisation de l'ancienne API ne devrait pas avoir effets secondaires inattendus sur la nouvelle API.

Si l'une des méthodes existantes de l'API est remplacée par les nouvelles, ne les supprimez pas immédiatement. Marquez-les comme obsolètes et expliquez ce qui doit être utilisé à la place. Cela avertit les utilisateurs de votre code que les futures versions pourraient ne plus le prendre en charge au lieu de casser leur code sans avertissement.

Si les nouvelles et anciennes API sont incompatibles et ne peuvent pas vivre ensemble sans effets secondaires indésirables, séparez-les et documentez que si la nouvelle API doit être adoptée, l'ancienne API doit être complètement retirée. C'est moins souhaitable car il y aura toujours quelqu'un qui essaiera d'utiliser les deux et sera frustré quand cela ne fonctionnera pas.

Étant donné que vous avez posé des questions sur .NET en particulier, vous pouvez lire cet article sur la dépréciation dans .NET, qui renvoie à ObsoleteAttribute(utilisé dans l'exemple suivant):

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
la source
2

Les changements d'interface publique impliquent une rupture. La stratégie courante consiste à ne les faire que sur les versions majeures et après une période de gel (donc cela ne se produit pas sur un coup de tête). Vous pouvez vous en tirer sans casser vos clients si vous ajoutez des ajouts dans une nouvelle interface (et votre implémentation peut fournir les deux sur la même classe). Ce n'est pas idéal, et si vous continuez à le faire, vous aurez un gâchis.

Avec d'autres types de modifications (suppression de méthodes, modification de signatures), vous êtes bloqué.

Tamás Szelei
la source
2
Vous pouvez de manière préventive réserver un préfixe pour les futurs noms de méthode et avertir tous les utilisateurs qu'ils ne doivent pas utiliser cet espace de noms, mais même cela fait une API inélégante. En général, parent est tout à fait raison: l' enlèvement (et souvent plus) des méthodes vont casser les utilisateurs existants, et il n'y a rien que vous pouvez faire à ce sujet , sauf le plan à bon escient.
Kilian Foth
1

Une interface est un contrat, elle ne doit donc pas avoir de version. Que se passe-t-il si un joueur de football obtient un nouveau contrat? L'ancien est-il toujours valide? Non. Si l'on change d'interface, le contrat change et le contrat précédent (interface) n'est plus valide.

Bien que vous puissiez utiliser la stratégie IFoo2, cela finira par devenir compliqué lorsque vous aurez:

  • IFoo2
  • IFoo3
  • IFoo4
  • etc.

Beurk.

Une API est différente. Je donne la bibliothèque de code à utiliser. Le mois prochain, je vous donne une bibliothèque mise à jour. Comme l'a dit une autre affiche, ne cassez pas ce que j'utilise déjà, ajoutez simplement de nouvelles fonctionnalités / méthodes.

Si vous voulez versionner quelque chose, utilisez une classe abtract au lieu d'une interface.

Jon Raynor
la source