Jak ewoluujesz i wersja interfejsu?

22

Załóżmy, że masz interfejs IFoo:

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

W wersji 2 interfejsu API musisz dodać metodę Glargdo tego interfejsu. Jak to zrobić, nie psując istniejących użytkowników interfejsu API i zachowując zgodność wsteczną? Jest to skierowane głównie do platformy .NET, ale może również dotyczyć innych platform i języków.

Thecoop
źródło
Możesz dodać bez problemów. Problemy pojawiają się, gdy zmieniasz / usuwasz coś, co już tam było.
Rig
1
@Rig: Przynajmniej w języku C # pojawi się błąd kompilacji, jeśli dodasz metodę do interfejsu i nie dodasz go do klas, które implementują ten interfejs.
Malice,
Cóż, to prawda. Zastanawiałem się nad scenariuszem klasy użytkownika, który może mieć minimalny chaos w porównaniu ze zmianą podpisu metody lub usunięciem jednego. Więc przypuszczam, że może to prowadzić do pewnych prac, jeśli potrzebujesz dodać do swojego interfejsu.
Rig

Odpowiedzi:

9

W wersji 2 interfejsu API musisz dodać metodę Glargdo tego interfejsu.

Czemu?

Interfejsy zdefiniowane do użytku z interfejsem API mają dwie zupełnie różne role:

  1. Odwrócenie zależności - takie interfejsy są używane przez interfejs API. Pozwalają kodowi klienta tworzyć wtyczki itp.
  2. Abstrakcja - takie interfejsy są zwracane przez interfejs API i ukrywają szczegóły implementacji zwracanych obiektów.

Teraz dla danej wersji interfejsu API ten sam interfejs może działać jako jedno i drugie. Jednak w przyszłych wersjach można to oddzielić.

  1. Chcesz wyodrębnić więcej informacji z używanego interfejsu. Aby zwiększyć wydajność lub dodać elastyczność lub cokolwiek innego. Zdefiniuj nowy interfejs, prawdopodobnie wywodzący się ze starego, i zbuduj oddzielną metodę, która go wykorzysta. AFAIK większość języków .NET pozwala na przeładowanie metod, więc może się to zdarzyć bez dodawania dużego bałaganu.
  2. Chcesz „zwrócić więcej”, tj. Wyodrębnić „bogatszy” obiekt z twojego API. Tutaj masz dwie możliwości:

    • Można rozsądnie założyć, że kod klienta nie będzie miał własnych implementatorów interfejsu. Przy takim założeniu można bezpiecznie dodawać rozszerzenia do istniejącego interfejsu.
    • Zdefiniuj nowy interfejs, jeśli to możliwe, oparty na poprzednim. Jeśli takie wyprowadzenie jest niemożliwe, utwórz osobne metody w celu zapytania o instancje nowego interfejsu lub użyj kompozycji:

      interface MyNewInterface extends MyOldInterface { 
           FancyNewInterface getFancyShit();
      }
      
back2dos
źródło
15

DirectX dodał numery wersji do swoich interfejsów. W twoim przypadku rozwiązaniem byłoby coś takiego

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

Interfejs API nadal odnosi się do IFoo i IFoo2 tylko w metodach itp., W których wymagana jest funkcjonalność IFoo2.

Implementacja API powinna sprawdzić w istniejących (= wersja 1) metodach, czy obiekt parametru IFoo faktycznie implementuje IFoo2, jeśli semantyka metody jest inna dla IFoo2.

devio
źródło
3

Dodanie nowej metody (lub metod) do interfejsu API powinno odbywać się w taki sposób, aby nie miało żadnych skutków ubocznych dla istniejącego interfejsu API. Co najważniejsze, ktoś, kto nadal korzysta ze starego interfejsu API tak, jakby nowy interfejs API nie istnieje, nie powinien mieć na niego wpływu. Korzystanie ze starego interfejsu API również nie powinno mieć nieoczekiwanych skutków ubocznych dla nowego interfejsu API.

Jeśli którakolwiek z istniejących metod interfejsu API zostanie zastąpiona nowymi, nie usuwaj ich od razu. Oznacz je jako przestarzałe i podaj wyjaśnienie, czego należy użyć w zamian. Dzięki temu użytkownicy Twojego kodu ostrzegają, że przyszłe wersje mogą go nie obsługiwać zamiast łamać kod bez ostrzeżenia.

Jeśli nowe i stare interfejsy API są niezgodne i nie mogą żyć razem bez niepożądanych efektów ubocznych, rozdziel je i udokumentuj, że jeśli nowy interfejs API ma zostać przyjęty, stary interfejs API musi zostać całkowicie wycofany. Jest to mniej pożądane, ponieważ zawsze znajdzie się ktoś, kto spróbuje użyć obu i denerwuje się, gdy to nie działa.

Ponieważ zapytałeś konkretnie o .NET, możesz przeczytać ten artykuł o wycofaniu w .NET, który prowadzi do ObsoleteAttribute(użytego w poniższym przykładzie):

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
źródło
2

Zmiany w interfejsie publicznym wiążą się z awarią. Powszechną strategią jest robienie tego tylko w głównych wersjach i po okresie zamrożenia (więc nie dzieje się to na zasadzie kaprysu). Możesz uciec bez rozbijania klientów, jeśli dodajesz dodatki do nowego interfejsu (a twoja implementacja może zapewnić oba w tej samej klasie). To nie jest idealne, a jeśli będziesz to robić dalej, będziesz mieć bałagan.

Jednak w przypadku innych rodzajów modyfikacji (usuwanie metod, zmiana podpisów) utkniesz.

Tamás Szelei
źródło
2
Możesz z wyprzedzeniem zarezerwować prefiks dla przyszłych nazw metod i ostrzec wszystkich użytkowników, że nie powinni używać tej przestrzeni nazw, ale nawet to powoduje, że API jest nieeleganckie. W ogóle, rodzic ma absolutną rację: usuwanie (i często dodatek) metod będzie łamać istniejących użytkowników, i nic nie można zrobić, że poza planem go mądrze.
Kilian Foth,
1

Interfejs jest umową, dlatego nie powinien mieć wersji. Co się stanie, jeśli piłkarz otrzyma nową umowę? Czy stary nadal jest ważny? Nie. Jeśli ktoś zmieni interfejs, umowa ulegnie zmianie, a poprzednia umowa (interfejs) przestanie obowiązywać.

Chociaż możesz użyć strategii IFoo2, w końcu stanie się to bałagan, gdy:

  • IFoo2
  • IFoo3
  • IFoo4
  • itp.

Fuj

Interfejs API jest inny. Daję bibliotekę kodu do użycia. W następnym miesiącu dam wam zaktualizowaną bibliotekę. Jak powiedział inny plakat, nie psuj tego, czego już używam, po prostu dodaj nową funkcjonalność / metody.

Jeśli chcesz coś zaktualizować, użyj klasy abtract zamiast interfejsu.

Jon Raynor
źródło