2011-01-27 13 views
5

Sto convertendo un codice Java in Scala, cercando di rendere il codice il più idiomatico possibile.Modo idiomatico per usare le opzioni in Scala

Così, ora ho del codice usando Opzioni invece di valori nullable, e mi chiedo se le cose siano di scala o se ho torto. Quindi, potresti per favore criticare il seguente snippet di codice?

Le aree in cui sono specificamente alla ricerca di un feedback sono:

  • L'uso di un oggetto associato come una fabbrica, dando 2 opzioni a seconda se vogliamo passare le opzioni o stringhe: è il costruttore String bene, o dovremmo sempre esporre il fatto che si tratta di un'opzione?
  • L'uso delle precondizioni: esistono modi migliori per affermare il fatto che alpha3Code e nome sono obbligatori e deve essere passata un'opzione non nulla per alpha2Code? (Sto ricorrendo a Guava per gli utils di stringa, poiché non ho trovato nulla nell'API di Scala)
  • L'implementazione di hashCode, equals e toString. equals e toString delegato di nuovo a Guava, mentre equals usa il pattern matching. Esiste un modo più scalare?
  • So che avrei potuto utilizzare le classi Case, che avrebbero creato implementazioni predefinite, ma sono principalmente interessato a imparare come implementare quelle per i casi in cui le classi di casi non possono essere utilizzate.

Grazie mille!

package com.sirika.openplacesearch.api.language 

import com.google.common.base.Objects 
import com.google.common.base.Strings 

object Language { 
    def apply(name : String, alpha3Code : String, alpha2Code : Option[String]) = new Language(name, alpha3Code, alpha2Code) 
    def apply(name : String, alpha3Code : String, alpha2Code : String = null) = new Language(name, alpha3Code, Option(alpha2Code)) 
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code) 
} 


class Language(val name : String, val alpha3Code : String, val alpha2Code : Option[String]) { 
    require(!Strings.isNullOrEmpty(alpha3Code)) 
    require(!Strings.isNullOrEmpty(name)) 
    require(alpha2Code != null) 

    override def hashCode(): Int = Objects.hashCode(alpha3Code) 

      override def equals(other: Any): Boolean = other match { 
     case that: Language => this.alpha3Code == that.alpha3Code 
     case _ => false 
    } 

    override def toString() : String = Objects.toStringHelper(this) 
     .add("name", name)  
     .add("alpha3", alpha3Code) 
     .add("alpha2", alpha2Code) 
     .toString() 
} 
+1

Il "trucco" di utilizzare le opzioni è quello di uniche opzioni di utilizzo e la forza del consumatore a fare lo stesso ;-) Naturalmente questo non è sempre pratico quando si ha a che fare con Java (ick!). Benvenuti in SO. –

+0

Non credo che require (alpha2Code! = Null) possa mai fallire poiché alpha2Code è un'opzione – Azzie

risposta

4

Penso che si dovrebbe esporre solo Option[String] nel metodo di fabbrica. Per esempio io, come utente della tua biblioteca, mi chiederò anche quale metodo di fabbrica dovrei usare. E molto probabilmente userò Option.

Scala ci offre abbastanza strumenti per semplificare le nostre vite. Ad esempio è possibile utilizzare di default per l'opzione in questo modo:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
new Language(name, alpha3Code, alpha2Code) 

Se io, ancora una volta come utente della libreria, voglio passare solo stringa senza avvolgendolo in Some ogni tempo, posso scrivere la mia conversione implicita come questo :

implicit def anyToOption[T](t: T): Option[T] = Some(t) 

o anche (se io personalmente uso null):

implicit def anyToOption[T](t: T): Option[T] = 
if (t == null) None else Some(t) 

Ma io credo, se far rispettare l'opzione, che renderà la vostra API più solido e chiaro.

+3

In realtà, esiste un modo più semplice per racchiudere un oggetto in 'Option' in modo che il valore' null' diventi 'None': 'Option (t)' (invece di 'Some (t)') – Madoc

4

Si dovrebbe evitare null a meno che non ci sia un buon motivo per non farlo. Così com'è, potresti aver appena scritto questo:

def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, Option(alpha2Code)) 
def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code, None) 

Le precondizioni vanno bene. Potresti scrivere così:

require(Option(alpha3Code) exists (_.nonEmpty)) 
require(Option(name) exists (_.nonEmpty)) 

Non necessariamente un miglioramento, però.

A String ha , quindi non capisco perché si sta chiamando un altro metodo per generare un codice hash invece di chiamare semplicemente alpha3Code.hashCode. Penso però che ci sia qualcosa nell'API di Scala. Non sono sicuro.

Il codice equals dovrebbe avere un metodo canEqual, a meno che non si effettua la classe sealed o final. pattern match è praticamente il modo di farlo, anche se si potrebbe avere scritto in questo modo data la presenza di un estrattore:

case Language(_, `alpha3Code`, _) => true 

Ma il modo in cui lo ha scritto è più o meno il modo in cui di solito è scritto.

+0

Le altre risposte sembrano enfatizzare il fatto che dovrei effettivamente forzare il consumatore a usare il costruttore Option. Sei d'accordo con questa affermazione? –

+0

Per quanto riguarda hashCode, è vero che in questo caso, potrei usare direttamente alpha3Code.hashCode, non ci ho nemmeno pensato perché ho l'abitudine di usare sempre la sintassi guava, che consente l'aggiunta di più chiavi: Objects.hashCode (alpha3Code, nome) –

+0

E buon punto riguardo al metodo canEqual. Non ne ero consapevole. Per gli altri che non conoscono la canEqual, puoi fare riferimento a http://books.google.com/books?id=MFjNhTjeQKkC&pg=PA555&lpg=PA555&dq=scala+canEqual+method&source=bl&ots=FKukUGGNvo&sig=0rF3cRgwHVGZ22ggxX5j23nM32w&hl=en&ei=As1CTaWhMoPGlQfknZ35Dw&sa= X & oi = book_result & ct = result & resnum = 3 & ved = 0CCcQ6AEwAg # v = onepage & q = scala% 20canEqual% 20method & f = false –

-1

Non mi piacciono le opzioni: aggiungono un livello di riferimento indiretto che non è necessario e confonde in molti casi. Non mi piace ancora di più i null, quindi capisco che spesso l'uso delle Opzioni è giustificato. Tuttavia, dovresti sempre vedere se esiste un modo più naturale per eliminare l'uso di Option in un'interfaccia.

I parametri predefiniti o i sovraccarichi separati sono spesso un'opzione migliore. Così mi piacerebbe riscrivere il codice come questo:

package com.sirika.openplacesearch.api.language 

import com.google.common.base.Strings 
import com.google.common.base.Objects 

object Language { 
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code) 
    def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code) 
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code) 
} 


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) { 
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code)) 
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None) 

    require(!Strings.isNullOrEmpty(alpha3Code)) 
    require(!Strings.isNullOrEmpty(name)) 

    override def hashCode = alpha3Code.hashCode 

    override def equals(other: Any) = other match { 
     case that: Language => this.alpha3Code == that.alpha3Code 
     case _ => false 
    } 

    override def toString = MoreObjects.toStringHelper(this) 
     .add("name", name)  
     .add("alpha3", alpha3Code) 
     .add("alpha2", alpha2Code) 
     .toString() 
} 

Guava docs

Problemi correlati