2015-06-17 13 views
10

Kotlin ha delegato proprietà che è una funzionalità molto bella. Ma a volte i metodi get() e set() non sono sufficienti. Diciamo che voglio creare pigramente un oggetto Closeable e chiuderlo in seguito. Ecco un esempio di come tali beni delegato potrebbe essere implementato:Accesso delegato proprietà in Kotlin

fun <T : Closeable> closeableLazy(initializer:() -> T) = 
     CloseableLazyVal(initializer) 

class CloseableLazyVal<T : Closeable>(
    private val initializer:() -> T 
) : ReadOnlyProperty<Any?, T> { 

    private var value: T? = null 

    override fun get(thisRef: Any?, desc: PropertyMetadata): T { 
     if (value == null) { 
      value = initializer() 
     } 
     return value 
    } 

    fun close() { 
     value?.close() 
    } 
} 

ed è così che vorrei usarlo:

private val stream by closeableLazy { FileOutputStream("/path/to/file") } 

fun writeBytes(bytes: ByteArray) { 
    stream.write(bytes) 
} 

override fun close() { 
    stream::delegate.close() // This line will not compile 
} 

Sfortunatamente, questo approccio non funziona perché sembra che Kotlin doesn Permettere di accedere direttamente ai delegati di proprietà. C'è un modo per fare ciò che voglio? O ci sono piani per aggiungere tale funzionalità a Kotlin perché sarebbe una caratteristica così bella.

+1

Tra l'altro, non è necessario l'invito a lasciare. Basta usare il valore? .close() –

+0

@cypressious, hai ragione, grazie. – Michael

risposta

6

Ok, così mi si avvicinò con la seguente soluzione:

fun <T : Closeable> closeableLazy(initializer:() -> T) = 
     CloseableLazyVal(initializer) 

class CloseableLazyVal<T : Closeable>(
     private val initializer:() -> T 
) : ReadOnlyProperty<CloseableDelegateHost, T> { 

    private var value: T? = null 

    override fun get(thisRef: CloseableDelegateHost, desc: PropertyMetadata): T { 
     if (value == null) { 
      value = initializer() 
      thisRef.registerCloseable(value!!) 
     } 
     return value!! 
    } 

} 

interface CloseableDelegateHost : Closeable { 
    fun registerCloseable(prop : Closeable) 
} 

class ClosableDelegateHostImpl : CloseableDelegateHost { 

    val closeables = arrayListOf<Closeable>() 

    override fun registerCloseable(prop: Closeable) { 
     closeables.add(prop) 
    } 

    override fun close() = closeables.forEach { it.close() } 
} 

class Foo : CloseableDelegateHost by ClosableDelegateHostImpl() { 
    private val stream by closeableLazy { FileOutputStream("/path/to/file") } 

    fun writeBytes(bytes: ByteArray) { 
     stream.write(bytes) 
    } 

} 

avviso, che il metodo get della struttura ha un parametro thisRef. Richiedo che erediti da CloseableDelegateHost che chiuderà qualsiasi Closeable s registrato quando viene chiuso. Per semplificare l'implementazione, delegare questa interfaccia a una semplice implementazione basata su elenchi.

UPDATE (copiato da commenti): mi sono reso conto, si può semplicemente dichiarare il delegato come una proprietà separata e quindi delegare la seconda proprietà ad esso. In questo modo è possibile accedere facilmente al delegato stesso.

private val streamDelegate = closeableLazy { FileOutputStream("/path/to/file") } 
private val stream by streamDelegate 

fun writeBytes(bytes: ByteArray) { 
    stream.write(bytes) 
} 

override fun close() { 
    streamDelegate.close() 
} 
+1

grazie per la risposta. La tua soluzione è carina e risolve il problema in questo caso particolare ma non funziona in tutti i casi. Diciamo, per esempio, che voglio chiudere diversi oggetti 'Closeable' in momenti diversi. Per gestire tale situazione devo aggiungere qualche tipo di chiave alla tua implementazione per rendere 'close()' più granulare. A mio avviso, tale funzionalità deve essere fornita dal linguaggio stesso senza tutti questi hack. – Michael

+1

Un altro problema che di solito devo affrontare è quando ho bisogno di verificare se la proprietà 'Delegate.lazy' è stata inizializzata. Questo problema potrebbe essere facilmente risolto se avessimo accesso al delegato della proprietà, ma tutti i walk-around sembrano essere brutti. – Michael

+2

Mi sono reso conto, puoi solo dichiarare il delegato come una proprietà separata e quindi delegare la seconda proprietà ad esso. In questo modo è possibile accedere facilmente al delegato stesso. –

5

In Kotlin 1.1 (dal beta 2), i delegati possono essere recuperati dalle proprietà, in modo da ora è possibile scrivere

override fun close() { 
    (::stream.apply { isAccessible = true }.getDelegate() 
     as CloseableLazyVal<*>).close() 
} 
+0

Richiede però la libreria Kotlin Reflect –