programowanieProgramista Androida

Opisz różnice między klasami abstrakcyjnymi a interfejsami w Kotlinie. Kiedy należy stosować które podejście, jakie są cechy implementacji w języku i jakie są szczegóły dziedziczenia?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Kotlinie klasy abstrakcyjne (abstract class) i interfejsy (interface) są używane do deklaracji abstrakcyjnej logiki, ale istnieją między nimi istotne różnice:

  • Klasa abstrakcyjna może zawierać stany (pola), implementacje metod oraz metody abstrakcyjne. Klasa może dziedziczyć tylko jedną klasę abstrakcyjną (lub zwykłą). Klasy abstrakcyjne mogą mieć konstruktory.
  • Interfejs może zawierać deklaracje właściwości (bez przechowywania stanu!), domyślne implementacje metod oraz metody abstrakcyjne. Od wersji Kotlin 1.1 interfejs może zawierać implementacje właściwości, ale nie mogą one mieć stanu. Klasa może implementować wiele interfejsów.
  • Interfejsy nie mogą zawierać stanu (backing field), podczas gdy klasy abstrakcyjne mogą.

Przykład kodu

abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("I am $name") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("Meow") override fun move() = println("Cat moves") }

Pytanie z podchwytliwością.

Czy interfejs w Kotlinie może zawierać stany (backing field)?

Większość odpowiada, że można deklarować właściwości w interfejsie, ale wciąż nie mają one backing field — tylko gettery/settery bez przechowywania wartości. Nawet jeśli napiszemy get() = ... — wartość będzie obliczana za każdym razem.

Przykład:

interface MyInterface { val value: Int get() = 42 // brak przechowywania wartości, obliczanie na bieżąco }

Przykłady rzeczywistych błędów z powodu braku znajomości szczegółów tematu.


Historia

Na dużym projekcie starszy programista próbował przechowywać stan w interfejsie (na przykład licznik), oczekując, że właściwość var count: Int zdobędzie backing field. W rezultacie wszystkie klasy implementujące interfejs musiały implementować przechowywanie wartości w różny sposób, co prowadziło do niestabilnej logiki (dane ginęły, jeśli zapominali o nadpisaniu settera).


Historia

Jeden z programistów użył klasy abstrakcyjnej tam, gdzie wymagana była wielokrotna implementacja różnych strategii zachowania (wzorzec strategii). W wyniku powstał problem: tylko jedna klasa bazowa w hierarchii, ale dla różnych aspektów zachowania wymagana była kompozycja. Konieczna była zmiana architektury na interfejsy.


Historia

Zespół próbował dziedziczyć od dwóch klas abstrakcyjnych (jedna z logiką, druga z wspólnymi narzędziami), co jest niemożliwe w Kotlinie. Problem ujawnił się na etapie rozszerzania aplikacji. Prowadziło to do znacznego długu technicznego, ponieważ klasy musiały zostać pilnie zreorganizowane.