Come ti evolvi e versione un'interfaccia?

22

Supponiamo che tu abbia un'interfaccia IFoo :

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

Nella versione 2 dell'API, devi aggiungere un metodo Glarg a questa interfaccia. Come si fa a fare ciò senza rompere gli utenti API esistenti e mantenere la compatibilità con le versioni precedenti? Questo è principalmente rivolto a .NET, ma può essere applicato ad altri framework e amp; lingue pure.

    
posta thecoop 09.03.2012 - 09:43
fonte

5 risposte

9

In version 2 of your API, you need to add a method Glarg to this interface.

Perché?

Le interfacce definite per l'uso con un'API hanno due ruoli completamente diversi:

  1. Inversione di dipendenza: tali interfacce vengono utilizzate dalla tua API. Permettono al codice client di creare plugin ecc.
  2. Astrazione: tali interfacce vengono restituite dalla tua API e nascondono i dettagli di implementazione degli oggetti restituiti.

Ora per una versione data di un'API, la stessa interfaccia può funzionare come entrambe. Tuttavia, nelle versioni future, questo può essere disaccoppiato.

  1. Vuoi estrarre più informazioni dall'interfaccia che consumi. Per migliorare le prestazioni o aggiungere flessibilità o altro. Definire una nuova interfaccia, possibilmente derivata da quella precedente, e costruire un metodo separato che la consuma. La maggior parte dei linguaggi .NET di AFAIK consente l'overloading dei metodi, quindi questo può accadere senza aggiungere troppa confusione.
  2. Vuoi "restituire di più", ovvero l'astrazione di un oggetto "più ricco" dalla tua API. Qui hai due scelte:

    • Si può ragionevolmente presumere che il codice cliente non abbia i propri implementatori dell'interfaccia. In base a questa ipotesi è sicuro aggiungere le estensioni all'interfaccia esistente.
    • Definire una nuova interfaccia, se possibile derivata dalla precedente. Se tale derivazione è impossibile, creare metodi separati per interrogare le istanze della nuova interfaccia o utilizzare la composizione:

      interface MyNewInterface extends MyOldInterface { 
           FancyNewInterface getFancyShit();
      }
      
risposta data 09.03.2012 - 11:33
fonte
15

DirectX ha aggiunto i numeri di versione alle sue interfacce. Nel tuo caso, la soluzione sarebbe qualcosa come

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

L'API rimanda comunque a IFoo e a IFoo2 solo nei metodi ecc. dove è richiesta la funzionalità IFoo2.

L'implementazione API dovrebbe verificare nei metodi esistenti (= versione 1) se un oggetto parametro IFoo implementa effettivamente IFoo2, se la semantica del metodo è diversa per IFoo2.

    
risposta data 09.03.2012 - 09:55
fonte
3

L'aggiunta di un nuovo metodo (o metodi) alla tua API deve essere eseguita in modo tale da non avere effetti collaterali sull'API esistente. Ancora più importante, qualcuno che continua a utilizzare la vecchia API come se la nuova API non esistesse, non dovrebbe essere influenzato da essa. L'utilizzo della vecchia API non dovrebbe avere effetti secondari inattesi anche sulla nuova API.

Se uno dei metodi esistenti nell'API viene sostituito da quelli nuovi, non rimuoverli immediatamente. Contrassegnali come deprecati e fornisci una spiegazione su cosa invece dovrebbe essere usato. Ciò consente agli utenti del tuo codice di avvertire che le versioni future potrebbero non supportarlo più invece di infrangere il loro codice senza preavviso.

Se le API nuove e vecchie non sono compatibili e non possono convivere senza effetti collaterali indesiderati, separali e documenta che se la nuova API deve essere adottata, la vecchia API deve essere ritirata completamente. Questo è meno desiderabile in quanto ci sarà sempre qualcuno che tenta di usare entrambi e si sente frustrato quando non funziona.

Dato che hai chiesto informazioni su .NET in particolare, potresti voler leggere questo articolo sulla deprecazione in .NET, che collega al ObsoleteAttribute (utilizzato nell'esempio seguente):

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() { }
}
    
risposta data 09.03.2012 - 10:35
fonte
2

I cambiamenti dell'interfaccia pubblica comportano la rottura. La strategia comune è di farlo solo sulle versioni principali e dopo un periodo di fermo (quindi non accade per un capriccio). Si può andare via senza rompere i propri clienti se si aggiungono aggiunte in una nuova interfaccia (e l'implementazione può fornire entrambi sulla stessa classe). Non è l'ideale, e se continui a farlo, avrai un casino.

Con altri tipi di modifica (rimuovendo metodi, cambiando le firme), sei bloccato.

    
risposta data 09.03.2012 - 10:02
fonte
1

Un'interfaccia è un contratto, quindi non dovrebbe avere il controllo delle versioni. Cosa succede se un giocatore di calcio ottiene un nuovo contratto? Il vecchio è ancora valido? No. Se si cambia l'interfaccia, il contratto cambia e il contratto precedente (interfaccia) non è più valido.

Sebbene tu possa usare la strategia IFoo2, alla fine diventerà caotico quando hai:

  • IFoo2
  • IFoo3
  • IFoo4
  • ecc.

Yuck.

Un'API è diversa. Fornisco una libreria di codice da usare. Il mese prossimo ti do una libreria aggiornata. Come un altro poster ha detto, non rompere ciò che sto già usando, basta aggiungere nuove funzionalità / metodi.

Se vuoi qualcosa di versione, usa una classe di abtract invece di un'interfaccia.

    
risposta data 09.03.2012 - 21:18
fonte

Leggi altre domande sui tag