programowanieProgramista Kotlin

Czym są interfejsy sealed (sealed interfaces) w Kotlinie, jak działają w praktyce i czym różnią się od klas sealed?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Historia pytania

W pierwszych wersjach Kotlin do ograniczenia hierarchii typów używano klas sealed, które pozwalały programistom wyraźnie kontrolować dozwolone podtypy poprzez ograniczenie deklaracji dziedziczy jedynie w jednym pliku. W niektórych zadaniach to nie wystarczało, zwłaszcza gdy trzeba było opisać podobne hierarchie za pomocą interfejsów. W celu rozwiązania tego problemu, od wersji Kotlin 1.5, wprowadzono modyfikator sealed dla interfejsów.

Problem

Zwykłe interfejsy pozwalają na ich implementację w dowolnym miejscu projektu, co często przeszkadza w zapewnieniu absolutnej zamkniętości hierarchii typów i wykorzystaniu wyczerpujących sprawdzeń typów (exhaustive when). Może to prowadzić do błędów w czasie działania, uniemożliwiając statyczne sprawdzenie wszystkich wariantów.

Rozwiązanie

Interfejsy sealed pozwalają na ograniczenie listy klas-implementacji w ramach jednego pliku, zwiększając przewidywalność systemu typów i bezpieczeństwo dopasowywania wzorców (pattern matching).

sealed interface NetworkResult class Success(val data: String): NetworkResult class Failure(val error: Throwable): NetworkResult fun handle(result: NetworkResult) = when(result) { is Success -> println("Dane: ${result.data}") is Failure -> println("Błąd: ${result.error}") // Wszystkie warianty uwzględnione, 'else' niepotrzebne }

Kluczowe cechy:

  • Interfejsy sealed zezwalają na pojedynczą hierarchię: mogą dziedziczyć jedynie klasy/obiektu z tego samego pliku
  • Interfejsy sealed mogą być implementowane zarówno przez klasy, jak i obiekty
  • Zwiększają typowe bezpieczeństwo wyrażenia when bez konieczności wyraźnego przewidywania else

Pytania z pułapką.

Czy można dodać interfejs sealed do osobnego pliku lub zaimplementować go poza pierwotnym plikiem?

Nie. Wszystkie typy bezpośrednio implementujące interfejs sealed muszą być zadeklarowane w tym samym pliku, w przeciwnym razie kompilator zgłosi błąd.

Czy interfejs sealed może mieć podinterfejsy poza plikiem deklaracji?

Nie. Podinterfejsy także muszą znajdować się w tym samym pliku. Cała hierarchia jest zamknięta w ramach pliku.

Czy interfejsy sealed wpływają na wydajność w czasie działania?

Nie bezpośrednio, ale pozwalają stosować bezpieczne sprawdzenia typów na etapie kompilacji, co zmniejsza prawdopodobieństwo błędów w czasie działania, upraszcza kod i może pośrednio przyspieszyć testowanie.

Typowe błędy i antywzorce

  • Deklarowanie klas-implementacji interfejsu sealed poza plikiem, w którym zadeklarowano sam interfejs
  • Dodawanie zbyt wielu wariantów, co utrudnia utrzymanie
  • Używanie interfejsu sealed, gdy hierarchia wariantów może się zmieniać (nie wymagana jest zamkniętość wariantów)

Przykład z życia

Negatywny przypadek

Programista deklaruje interfejs sealed NetworkAction w jednym pliku, ale implementacje są rozproszone w różnych klasach i plikach. Problem ujawnia się późno: kompilator sygnalizuje naruszenie reguły, naprawienie struktury jest trudne, zwłaszcza w dużym projekcie.

Zalety:

  • Pozwala na zadeklarowanie nowych implementacji bez edytowania jednego pliku

Wady:

  • Narusza gwarancje bezpieczeństwa typów
  • Wymusza refaktoryzację kodu, co jest kosztowne czasowo

Pozytywny przypadek

Interfejs sealed OrderResult oraz klasy Success/Failure są zadeklarowane w jednym pliku. Sprawdzenie when jest zawsze wyczerpujące, żaden przypadek nie jest pominięty.

Zalety:

  • Bezpieczeństwo i kompletność obsługi
  • Poprawia jakość architektury

Wady:

  • Mniej elastyczna rozbudowa (wszystkie zmiany tylko przez ten plik)