Classi sigillate a Kotlin

1. Introduzione

In poche parole, il linguaggio Kotlin ha preso in prestito una serie di concetti da altri linguaggi funzionali per aiutare a scrivere codice più sicuro e più leggibile. Gerarchie sigillate è uno di questi concetti.

2. Che cos'è una classe Sealed?

Le classi sigillate ci consentono di correggere le gerarchie di tipi e vietano agli sviluppatori di creare nuove sottoclassi.

Sono utili quando si dispone di una gerarchia di ereditarietà molto rigida, con un insieme specifico di possibili sottoclassi e nessun altro. Il compilatore garantisce che solo le classi definite nello stesso file sorgente della classe sealed siano in grado di ereditare da esso.

Anche le classi Sealed sono implicitamente astratte . Dovrebbero essere trattati come tali per tutto il resto del codice, tranne per il fatto che nient'altro è in grado di implementarli.

Le classi Sealed possono avere campi e metodi definiti al loro interno, incluse funzioni sia astratte che implementate. Ciò significa che è possibile avere una rappresentazione di base della classe e quindi adattarla alle sottoclassi.

3. Quando utilizzare le classi Sealed?

Le classi sigillate sono progettate per essere utilizzate quando esiste un insieme molto specifico di possibili opzioni per un valore e dove ciascuna di queste opzioni è funzionalmente diversa - solo i tipi di dati algebrici.

I casi d'uso comuni potrebbero includere l'implementazione di una macchina a stati o nella programmazione monadica, che sta diventando sempre più popolare con l'avvento dei concetti di programmazione funzionale.

Ogni volta che hai più opzioni e differiscono solo nel significato dei dati, potresti utilizzare invece le classi Enum.

Ogni volta che hai un numero sconosciuto di opzioni, non puoi usare una classe sealed perché questo ti impedirà di aggiungere opzioni al di fuori del file sorgente originale.

4. Scrittura di classi sigillate

Cominciamo scrivendo la nostra classe sigillata - il buon esempio di tale gerarchia sigillata è un Opzionale da Java 8 - che può essere Some o None.

Quando si implementa questo, ha molto senso limitare la possibilità di creare nuove implementazioni: le due implementazioni fornite sono esaustive e nessuno dovrebbe aggiungere le proprie.

In quanto tale, possiamo implementarlo:

sealed class Optional { // ... abstract fun isPresent(): Boolean } data class Some(val value: V) : Optional() { // ... override fun isPresent(): Boolean = true } class None : Optional() { // ... override fun isPresent(): Boolean = false }

Ora è possibile garantire che ogni volta che si dispone di un'istanza di Opzionale , si dispone effettivamente di Some o None.

In Java 8, l'implementazione effettiva sembra diversa a causa dell'assenza di classi sigillate.

Possiamo quindi utilizzarlo nei nostri calcoli:

val result: Optional = divide(1, 0) println(result.isPresent()) if (result is Some) { println(result.value) }

La prima riga restituirà Some o None . Quindi emettiamo se abbiamo ottenuto o meno un risultato.

5. Utilizzare con quando

Kotlin supporta l'utilizzo di classi sigillate nei suoi costrutti when . Poiché esiste sempre un insieme esatto di possibili sottoclassi, il compilatore è in grado di avvisarti se qualche ramo non viene gestito, esattamente nello stesso modo in cui avviene per le enumerazioni.

Ciò significa che in tali situazioni, normalmente non è necessario un gestore catch-all, il che a sua volta significa che l'aggiunta di una nuova sottoclasse è automaticamente sicura: il compilatore ti avviserà immediatamente se non l'hai gestita e avrai bisogno per correggere tali errori prima di continuare.

L'esempio precedente può essere esteso per visualizzare il risultato o un errore a seconda del tipo restituito:

val message = when (result) { is Some -> "Answer: ${result.value}" is None -> "No result" } println(message)

Se uno dei due rami fosse mancante, questo non si compilerebbe e restituirebbe invece un errore di:

'when' expression must be exhaustive, add necessary 'else' branch

6. Riepilogo

Le classi sigillate possono essere uno strumento prezioso per il tuo toolbox di progettazione API. Consentire una gerarchia di classi ben nota e strutturata che può essere solo una di un insieme di classi previsto può aiutare a rimuovere un intero insieme di potenziali condizioni di errore dal codice, pur rendendo le cose facili da leggere e mantenere.

Come sempre, gli snippet di codice possono essere trovati su GitHub.