W Kotlinie klasy abstrakcyjne (abstract class) i interfejsy (interface) są używane do deklaracji abstrakcyjnej logiki, ale istnieją między nimi istotne różnice:
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") }
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 }
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.