ProgrammazioneSviluppatore Android

Descrivi le differenze tra classi astratte e interfacce in Kotlin. Quando utilizzare quale approccio, quali sono le caratteristiche di implementazione nel linguaggio e quali sono le sfumature dell'ereditarietà?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

In Kotlin classi astratte (abstract class) e interfacce (interface) vengono utilizzate per dichiarare logica astratta, ma ci sono differenze sostanziali tra di esse:

  • Una classe astratta può contenere stati (campi), implementazioni di metodi e metodi astratti. Una classe può ereditare solo una classe astratta (o normale). I costruttori possono essere presenti in una classe astratta.
  • Un interfaccia può contenere dichiarazioni di proprietà (senza memorizzare stato!), implementazioni predefinite di metodi e metodi astratti. A partire da Kotlin 1.1, un'interfaccia può contenere implementazioni di proprietà, ma queste non possono avere stato. Una classe può implementare molte interfacce.
  • Le interfacce non possono contenere stati (backing field), mentre le classi astratte possono.

Esempio di codice

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") }

Domanda trabocchetto.

Un'interfaccia in Kotlin può contenere stati (backing field)?

La maggior parte risponde che si possono dichiarare proprietà in un'interfaccia, ma esse non hanno comunque un backing field — solo getter/setter senza memorizzazione del valore. Anche se si scrive get() = ... — il valore verrà calcolato ogni volta.

Esempio:

interface MyInterface { val value: Int get() = 42 // nessuna memorizzazione del valore, calcolo al volo }

Esempi di errori reali a causa della mancata conoscenza delle sfumature dell'argomento.


Storia

In un grande progetto, uno sviluppatore senior cercava di memorizzare uno stato in un'interfaccia (ad esempio, un contatore), aspettandosi che la proprietà var count: Int avesse un backing field. Di conseguenza, tutte le classi implementatrici dell'interfaccia dovevano implementare la memorizzazione del valore in modi diversi, il che ha portato a logiche instabili (i dati andavano persi se si dimenticava di sovrascrivere il setter).


Storia

Uno degli sviluppatori usava una classe astratta dove era necessaria un'implementazione multipla di diverse strategie comportamentali (pattern strategia). Di conseguenza, nacque un problema: solo una classe base per gerarchia, ma per diversi aspetti comportamentali era necessaria la composizione. È stato necessario cambiare l'architettura in interfacce.


Storia

Il team tentava di ereditare due classi astratte (una con logica, l'altra con utilità comuni), il che è impossibile in Kotlin. Il problema è emerso già nella fase di estensione dell'applicazione. Questo ha portato a un significativo debito tecnico, poiché le classi dovevano essere riorganizzate in fretta.