programowanieProgramista Kotlin

Jakie są różnice między zwykłymi klasami, klasami abstrakcyjnymi a interfejsami w Kotlinie, kiedy i który instrument stosować?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania:

Kotlin łączy najlepsze cechy Javy i dziedzictwa funkcjonalnego JVM. Zwykłe klasy są ogłaszane jako standardowe struktury, klasy abstrakcyjne pozwalają na tworzenie niedookreślonych szablonów z domyślną implementacją, a interfejsy obsługują wielokrotne dziedziczenie zachowań bez stanu.

Problem:

Właściwy wybór między klasą, klasą abstrakcyjną a interfejsem określa architekturę aplikacji, granularność kodu i jego rozszerzalność. Niewłaściwe dziedziczenie prowadzi do trudności w testowaniu i przyszłych zmianach.

Rozwiązanie:

W Kotlinie:

  • Zwykła klasa: określa strukturę i zachowanie. Może być dziedziczona, jeśli jest zadeklarowana jako open.
  • Klasa abstrakcyjna: nie może być tworzona bezpośrednio. Może zawierać implementację i/lub abstrakcyjne (bez ciała) metody.
  • Interfejs: domyślnie otwarty, realizuje kontrakty, pozwala na implementację metod, ale nie przechowuje stanu (tylko właściwości bez pola backing field).

Przykład kodu:

interface Drawable { fun draw() } abstract class Shape(var color: String) : Drawable { abstract fun calcArea(): Double override fun draw() = println("Shape drawn") } class Circle(color: String, val radius: Double) : Shape(color) { override fun calcArea() = Math.PI * radius * radius }

Kluczowe cechy:

  • Interfejsy pozwalają na wielokrotną implementację, klasy abstrakcyjne – tylko jedną
  • Interfejsy nie mogą przechowywać stanu, klasy abstrakcyjne mogą
  • Klasa abstrakcyjna może implementować część interfejsów i zawierać własną wspólną logikę

Pytania z kruczkami.

Czy interfejsy mogą zawierać właściwości z backing field?

Nie, mogą definiować tylko sygnaturę właściwości, ale nie przechowywać danych – właściwości bez pola backing field.

Czy można dziedziczyć z kilku klas?

Nie, Kotlin obsługuje tylko pojedyncze dziedziczenie klas, ale wielokrotną implementację interfejsów.

Czy można zadeklarować konstruktor w interfejsie?

Nie, interfejs nie obsługuje konstruktorów, ponieważ nie przechowuje stanu – tylko kontrakt zachowania.

Typowe błędy i antywzorce

  • Użycie klasa abstrakcyjna zamiast interfejsu bez potrzeby
  • Przechowywanie stanu w interfejsie (niemożliwe, ale popełniane błędy podczas projektowania)
  • Projektowanie hierarchii klas, które utrudniają rozszerzalność

Przykład z życia

Negatywny przypadek

W aplikacji wszystkie wspólne funkcje przeniesiono do klasy abstrakcyjnej, nawet jeśli nie miały wewnętrznej logiki ani stanu, a potrzeba była tylko wspólnym kontraktem.

Zalety:

  • Jedno miejsce zmiany wspólnej logiki

Wady:

  • Problemy z wielokrotnym dziedziczeniem, trudna integracja z innymi strukturami

Pozytywny przypadek

Przeniesiono tylko potrzebne kontrakty do interfejsów, klasy abstrakcyjne ograniczono do wspólnych właściwości i metod wymagających implementacji.

Zalety:

  • Elastyczne rozszerzenie, modułowość, czyste kontrakty

Wady:

  • Wymaga projektowania we wczesnej fazie